// copyright 1993 Michael B. Johnson; some portions copyright 1994, MIT
// see COPYRIGHT for reuse legalities
//

#import "WW3DCamera.h"

#import "WWInterp.h" // this includes WWTCLKit.h 

#import "WW3DShape.h"
#import "WW3DShader.h"
#import "WW3DText.h"
#import "RIBColor.h"
#import "RIBTorus.h"
#import "WW3DLight.h"

#import "WW3DWell.h"
#import "WWSceneClock.h"  // has the sceneClock protocol - formalize this!
#import "WWEveParser.h"  // to work around NeXT's copyRIB: bug...

#import <mach/cthreads.h>

@implementation WW3DCamera

+ initialize { return [WW3DCamera setVersion:5], self; }

static char errBuf[1024]; 

/*
==========================================================================
subext() -- Remove extension from fname only if it is there.

==========================================================================
*/
static char 
*subext(fname, ext)
register char *fname, *ext;
{
  int fl, el;

	for ( fl = strlen(fname), el = strlen(ext);
		--el > -1 && --fl > -1 && fname[fl] == ext[el]; )
		;
	if (el == -1) 
	{  fname[fl] = '\0';
	}
	return(fname);
}


/*
==========================================================================
basename() -- deletes any prefix ending in `/' and the suffix.

==========================================================================
*/
static char 
*basename(str, sfx, dest)
char *str, *sfx, *dest;
{
char temp[1024];
char *p, *p1;
char *subext();

	p = p1 = temp;
	strcpy(p, str);
	while (*p) 
	{  if (*p++ == '/') 
           {  p1 = p;
	   }
	}
	strcpy (dest, subext(p1, sfx));
	return(dest);
}


static int
cmd_dumpRIBToFile(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char  *my_args = "filename";
  int	num_args = 2;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  [me dumpRIBToFile:argv[1]];

  return TCL_OK;
}

static int
cmd_display(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char  *my_args = "";
  int	num_args = 1;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  [me display];

  return TCL_OK;
}

static int
cmd_backgroundColor(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char     *my_args = "[{r g b}]";
  NXColor  backgroundColor;
  int      argc2;
  char     **argv2;
  float    red, green, blue;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if ((argc != 1) && (argc != 2) && (argc != 4))
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  if (argc == 1)
  {  backgroundColor = [me backgroundColor];
     sprintf(interp->result, "{%f %f %f}", 
	     NXRedComponent(backgroundColor), 
	     NXGreenComponent(backgroundColor), 
	     NXBlueComponent(backgroundColor));
     return TCL_OK;
  }
  if (argc == 2)
  {  Tcl_SplitList(interp, argv[1], &argc2, &argv2);
     if (argc2 != 3)
     {  sprintf(errBuf, "USAGE: %s %s (colors should have 3 components)", argv[0], my_args);
        Tcl_AppendResult(interp, errBuf, (char *)NULL);
        if (argv2) { free(argv2); }
	return TCL_ERROR;
     }
     red = (float)atof(argv2[0]);
     green = (float)atof(argv2[1]);
     blue = (float)atof(argv2[2]);
     free(argv2);
     backgroundColor = NXConvertRGBAToColor(red, green, blue, 1.0);
     [me setBackgroundColor:backgroundColor];
     backgroundColor = [me backgroundColor];
     sprintf(interp->result, "{%f %f %f}", 
	     NXRedComponent(backgroundColor), 
	     NXGreenComponent(backgroundColor), 
	     NXBlueComponent(backgroundColor));
     return TCL_OK;
  }
  if (argc == 4)
  {  red = (float)atof(argv[1]);
     green = (float)atof(argv[2]);
     blue = (float)atof(argv[3]);
     backgroundColor = NXConvertRGBAToColor(red, green, blue, 1.0);
     [me setBackgroundColor:backgroundColor];
     backgroundColor = [me backgroundColor];
     sprintf(interp->result, "{%f %f %f}", 
	     NXRedComponent(backgroundColor), 
	     NXGreenComponent(backgroundColor), 
	     NXBlueComponent(backgroundColor));
     return TCL_OK;
  }
  return TCL_ERROR;
}

static int
cmd_renderStyle(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char  *my_args = "[PointCloud|WireFrame|ShadedWireFrame|FacetedSolids|SmoothSolids]";
  int   renderStyle;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if ((argc != 1) && (argc != 2))
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  if (argc == 1)
  {  renderStyle = [me renderStyle];
     if (renderStyle == N3D_PointCloud)
     {  sprintf(interp->result, "PointCloud");
        return TCL_OK;
     }
     if (renderStyle == N3D_WireFrame)
     {  sprintf(interp->result, "WireFrame");
        return TCL_OK;
     }
     if (renderStyle == N3D_ShadedWireFrame)
     {  sprintf(interp->result, "ShadedWireFrame");
        return TCL_OK;
     }
     if (renderStyle == N3D_FacetedSolids)
     {  sprintf(interp->result, "FacetedSolids");
        return TCL_OK;
     }
     if (renderStyle == N3D_SmoothSolids)
     {  sprintf(interp->result, "SmoothSolids");
        return TCL_OK;
     }
     sprintf(interp->result, 
	     "Yikes! I have no mapping for renderStyle %d", 
	     renderStyle);
     return TCL_ERROR;
  }
  if (argc == 2)
  {  if (!strcmp(argv[1], "PointCloud"))
     {  [me setRenderStyle:N3D_PointCloud];
        [[me delegate] cameraParametersWereUpdated:me];
        return TCL_OK;
     }
     if (!strcmp(argv[1], "WireFrame"))
     {  [me setRenderStyle:N3D_WireFrame];
        [[me delegate] cameraParametersWereUpdated:me];
        return TCL_OK;
     }
     if (!strcmp(argv[1], "ShadedWireFrame"))
     {  [me setRenderStyle:N3D_ShadedWireFrame];
        [[me delegate] cameraParametersWereUpdated:me];
        return TCL_OK;
     }
     if (!strcmp(argv[1], "FacetedSolids"))
     {  [me setRenderStyle:N3D_FacetedSolids];
        [[me delegate] cameraParametersWereUpdated:me];
        return TCL_OK;
     }
     if (!strcmp(argv[1], "SmoothSolids"))
     {  [me setRenderStyle:N3D_SmoothSolids];
        [[me delegate] cameraParametersWereUpdated:me];
        return TCL_OK;
     }
   
     sprintf(errBuf, "ERROR: unknown surface type <%s>\nUSAGE: %s %s", argv[1], argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  return TCL_ERROR;
}



static int
cmd_movingRenderStyle(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char  *my_args = "[PointCloud|WireFrame|ShadedWireFrame|FacetedSolids|SmoothSolids]";
  int   movingRenderStyle;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if ((argc != 1) && (argc != 2))
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  if (argc == 1)
  {  movingRenderStyle = [me movingRenderStyle];
     if (movingRenderStyle == N3D_PointCloud)
     {  sprintf(interp->result, "PointCloud");
        return TCL_OK;
     }
     if (movingRenderStyle == N3D_WireFrame)
     {  sprintf(interp->result, "WireFrame");
        return TCL_OK;
     }
     if (movingRenderStyle == N3D_ShadedWireFrame)
     {  sprintf(interp->result, "ShadedWireFrame");
        return TCL_OK;
     }
     if (movingRenderStyle == N3D_FacetedSolids)
     {  sprintf(interp->result, "FacetedSolids");
        return TCL_OK;
     }
     if (movingRenderStyle == N3D_SmoothSolids)
     {  sprintf(interp->result, "SmoothSolids");
        return TCL_OK;
     }
     sprintf(interp->result, 
	     "Yikes! I have no mapping for movingRenderStyle %d", 
	     movingRenderStyle);
     return TCL_ERROR;
  }
  if (argc == 2)
  {  if (!strcmp(argv[1], "PointCloud"))
     {  [me setMovingRenderStyle:N3D_PointCloud];
        [[me delegate] cameraParametersWereUpdated:me];
        return TCL_OK;
     }
     if (!strcmp(argv[1], "WireFrame"))
     {  [me setMovingRenderStyle:N3D_WireFrame];
        [[me delegate] cameraParametersWereUpdated:me];
        return TCL_OK;
     }
     if (!strcmp(argv[1], "ShadedWireFrame"))
     {  [me setMovingRenderStyle:N3D_ShadedWireFrame];
        [[me delegate] cameraParametersWereUpdated:me];
        return TCL_OK;
     }
     if (!strcmp(argv[1], "FacetedSolids"))
     {  [me setMovingRenderStyle:N3D_FacetedSolids];
        [[me delegate] cameraParametersWereUpdated:me];
        return TCL_OK;
     }
     if (!strcmp(argv[1], "SmoothSolids"))
     {  [me setMovingRenderStyle:N3D_SmoothSolids];
        [[me delegate] cameraParametersWereUpdated:me];
        return TCL_OK;
     }
   
     sprintf(errBuf, "ERROR: unknown surface type <%s>\nUSAGE: %s %s", argv[1], argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  return TCL_ERROR;
}



static int
cmd_tesselation(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char  *my_args = "[tesselation]";


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if ((argc != 1) && (argc != 2))
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  if (argc == 1)
  {  sprintf(interp->result, "%f", [me tesselation]);
     return TCL_OK;
  }
  if (argc == 2)
  {  [me setTesselation:(float)atof(argv[1])];
     sprintf(interp->result, "%f", [me tesselation]);
     return TCL_OK;
  }
  return TCL_ERROR;
}

static int
cmd_lowRezTesselation(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char  *my_args = "[lowRezTesselation]";


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if ((argc != 1) && (argc != 2))
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  if (argc == 1)
  {  sprintf(interp->result, "%f", [me lowRezTesselation]);
     return TCL_OK;
  }
  if (argc == 2)
  {  [me setLowRezTesselation:(float)atof(argv[1])];
     sprintf(interp->result, "%f", [me lowRezTesselation]);
     return TCL_OK;
  }
  return TCL_ERROR;
}

static int
cmd_fieldOfView(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char  *my_args = "[fov]";


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if ((argc != 1) && (argc != 2))
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  if (argc == 1)
  {  sprintf(interp->result, "%f", [me fieldOfView]);
     return TCL_OK;
  }
  if (argc == 2)
  {  [me setFieldOfViewByAngle:(float)atof(argv[1])];
     sprintf(interp->result, "%f", [me fieldOfView]);
     return TCL_OK;
  }
  return TCL_ERROR;
}

static int
cmd_setEyeAtTowardRoll(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char     *my_args = "fromPoint toPoint aRollAngle";
  int	   num_args = 4, argc2;
  RtPoint  fromPoint, toPoint;
  float    aRollAngle;
  char     **argv2;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  Tcl_SplitList(interp, argv[1], &argc2, &argv2); // fromPoint
  if (argc2 != 3)
  {  sprintf(errBuf, "USAGE: %s %s (points should have 3 components)", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     if (argv2) { free(argv2); }
     return TCL_ERROR;
  }
  N3D_XComp(fromPoint) = (float)atof(argv2[0]);
  N3D_YComp(fromPoint) = (float)atof(argv2[1]);
  N3D_ZComp(fromPoint) = (float)atof(argv2[2]);
  free(argv2);

  Tcl_SplitList(interp, argv[2], &argc2, &argv2); // fromPoint
  if (argc2 != 3)
  {  sprintf(errBuf, "USAGE: %s %s (points should have 3 components)", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     if (argv2) { free(argv2); }
     return TCL_ERROR;
  }
  N3D_XComp(toPoint) = (float)atof(argv2[0]);
  N3D_YComp(toPoint) = (float)atof(argv2[1]);
  N3D_ZComp(toPoint) = (float)atof(argv2[2]);
  free(argv2);

  aRollAngle = (float)atof(argv[3]);

  [me setEyeAt:fromPoint toward:toPoint roll:aRollAngle];

  return TCL_OK;
}

static int
cmd_moveEyeBy(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char  *my_args = "sDistance tDistance uDistance";
  int	num_args = 4;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  [me moveEyeBy:(float)atof(argv[1]) :(float)atof(argv[2]) :(float)atof(argv[3])]; 
  [[me delegate] cameraParametersWereUpdated:me];
  return TCL_OK;
}

static int
cmd_getEyePoint(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char     *my_args = "";
  int	   num_args = 1;
  RtPoint  eyePoint, viewPoint;
  float    aRollAngle;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  [me getEyeAt:&eyePoint toward:&viewPoint roll:&aRollAngle];
  sprintf(interp->result, 
	  "%f %f %f", 
	  N3D_XComp(eyePoint), N3D_YComp(eyePoint), N3D_ZComp(eyePoint));
  return TCL_OK;
}

static int
cmd_getViewPoint(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char     *my_args = "";
  int	   num_args = 1;
  RtPoint  eyePoint, viewPoint;
  float    aRollAngle;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  [me getEyeAt:&eyePoint toward:&viewPoint roll:&aRollAngle];
  sprintf(interp->result, 
	  "%f %f %f", 
	  N3D_XComp(viewPoint), N3D_YComp(viewPoint), N3D_ZComp(viewPoint));
  return TCL_OK;
}

static int
cmd_getRollAngle(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char     *my_args = "";
  int	   num_args = 1;
  RtPoint  eyePoint, viewPoint;
  float    aRollAngle;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  [me getEyeAt:&eyePoint toward:&viewPoint roll:&aRollAngle];
  sprintf(interp->result, "%f", aRollAngle);
  return TCL_OK;
}

static int
cmd_getEyeAtTowardRoll(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char     *my_args = "";
  int	   num_args = 1;
  RtPoint  eyePoint, viewPoint;
  float    aRollAngle;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  [me getEyeAt:&eyePoint toward:&viewPoint roll:&aRollAngle];
  sprintf(interp->result, 
	  "{%f %f %f} {%f %f %f} %f", 
	  N3D_XComp(eyePoint), N3D_YComp(eyePoint), N3D_ZComp(eyePoint),
	  N3D_XComp(viewPoint), N3D_YComp(viewPoint), N3D_ZComp(viewPoint),
	  aRollAngle);
  return TCL_OK;
}

static int
cmd_setSurfaceTypeForAll(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char  *my_args = "PointCloud|WireFrame|ShadedWireFrame|FacetedSolids|SmoothSolids";
  int	num_args = 2;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  if (!strcmp(argv[1], "PointCloud"))
  {  [me setSurfaceTypeForAll:N3D_PointCloud chooseHider:YES];
     [[me delegate] cameraParametersWereUpdated:me];
     return TCL_OK;
  }
  if (!strcmp(argv[1], "WireFrame"))
  {  [me setSurfaceTypeForAll:N3D_WireFrame chooseHider:YES];
     [[me delegate] cameraParametersWereUpdated:me];
     return TCL_OK;
  }
  if (!strcmp(argv[1], "ShadedWireFrame"))
  {  [me setSurfaceTypeForAll:N3D_ShadedWireFrame chooseHider:YES];
     [[me delegate] cameraParametersWereUpdated:me];
     return TCL_OK;
  }
  if (!strcmp(argv[1], "FacetedSolids"))
  {  [me setSurfaceTypeForAll:N3D_FacetedSolids chooseHider:YES];
     [[me delegate] cameraParametersWereUpdated:me];
     return TCL_OK;
  }
  if (!strcmp(argv[1], "SmoothSolids"))
  {  [me setSurfaceTypeForAll:N3D_SmoothSolids chooseHider:YES];
     [[me delegate] cameraParametersWereUpdated:me];
     return TCL_OK;
  }

  sprintf(errBuf, "ERROR: unknown surface type <%s>\nUSAGE: %s %s", argv[1], argv[0], my_args);
  Tcl_AppendResult(interp, errBuf, (char *)NULL);
  return TCL_ERROR;
}


static int
cmd_removeDefaultLights(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char  *my_args = "";
  int	num_args = 1;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  [me removeDefaultLights];
  [[me delegate] cameraParametersWereUpdated:me];

  return TCL_OK;
}

static int
cmd_restoreDefaultLights(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char  *my_args = "";
  int	num_args = 1;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  [me restoreDefaultLights];
  [[me delegate] cameraParametersWereUpdated:me];

  return TCL_OK;
}

static int
cmd_lightList(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char          *my_args = "";
  int	        num_args = 1, i;
  id            theLightList;
  N3DLight      *light;
  N3DLightType  lightType;
  char          aBuf[MAXPATHLEN];
  RtPoint       from, to;
  RtFloat       coneAngle, coneDelta, beamDistribution;
  NXColor       color;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  theLightList = [me lightList];
  
  Tcl_AppendResult(interp, "{", (char *)NULL);
  for (i = 0; i < [theLightList count]; i++)
  {  light = [theLightList objectAt:i];
     Tcl_AppendResult(interp, "{", (char *)NULL);

     sprintf(aBuf, "name %s", [light shapeName]);
     Tcl_AppendElement(interp, aBuf);

     lightType = [light type];
     switch (lightType)  
     {  case N3D_AmbientLight:  Tcl_AppendElement(interp, "type ambient");
	                        break;
        case N3D_PointLight:    Tcl_AppendElement(interp, "type point");
	                        break;
        case N3D_DistantLight:  Tcl_AppendElement(interp, "type distant");
	                        break;
        case N3D_SpotLight:     Tcl_AppendElement(interp, "type spot");
	                        break;
        default:                Tcl_AppendElement(interp, "type unknown");
	                        break;
     }

     [light getFrom:&from to:&to];
     sprintf(aBuf, "from %f %f %f", from[0], from[1], from[2]);
     Tcl_AppendElement(interp, aBuf);
     sprintf(aBuf, "to %f %f %f", to[0], to[1], to[2]);
     Tcl_AppendElement(interp, aBuf);

     sprintf(aBuf, "intensity %f", [light intensity]);
     Tcl_AppendElement(interp, aBuf);

     [light getConeAngle:&coneAngle coneDelta:&coneDelta beamDistribution:&beamDistribution];
     sprintf(aBuf, "coneAngle %f", coneAngle);
     Tcl_AppendElement(interp, aBuf);
     sprintf(aBuf, "coneDelta %f", coneDelta);
     Tcl_AppendElement(interp, aBuf);
     sprintf(aBuf, "beamDistribution %f", beamDistribution);
     Tcl_AppendElement(interp, aBuf);

     color = [light color];
     sprintf(aBuf, "color %f %f %f", 
	     NXRedComponent(color), NXGreenComponent(color), NXBlueComponent(color));
     Tcl_AppendElement(interp, aBuf);

     sprintf(aBuf, "isGlobal %d", (int)[light isGlobal]);
     Tcl_AppendElement(interp, aBuf);

     Tcl_AppendResult(interp, "}", (char *)NULL);
  }
  Tcl_AppendResult(interp, "}", (char *)NULL);
  return TCL_OK;
}


static int
cmd_otherLightList(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char          *my_args = "";
  int	        num_args = 1, i;
  id            theLightList;
  N3DLight      *light;
  N3DLightType  lightType;
  char          aBuf[MAXPATHLEN];
  RtPoint       from, to;
  RtFloat       coneAngle, coneDelta, beamDistribution;
  NXColor       color;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  theLightList = [me otherLightList];
  
  Tcl_AppendResult(interp, "{", (char *)NULL);
  for (i = 0; i < [theLightList count]; i++)
  {  light = [theLightList objectAt:i];
     Tcl_AppendResult(interp, "{", (char *)NULL);

     sprintf(aBuf, "name %s", [light shapeName]);
     Tcl_AppendElement(interp, aBuf);

     lightType = [light type];
     switch (lightType)  
     {  case N3D_AmbientLight:  Tcl_AppendElement(interp, "type ambient");
	                        break;
        case N3D_PointLight:    Tcl_AppendElement(interp, "type point");
	                        break;
        case N3D_DistantLight:  Tcl_AppendElement(interp, "type distant");
	                        break;
        case N3D_SpotLight:     Tcl_AppendElement(interp, "type spot");
	                        break;
        default:                Tcl_AppendElement(interp, "type unknown");
	                        break;
     }

     [light getFrom:&from to:&to];
     sprintf(aBuf, "from %f %f %f", from[0], from[1], from[2]);
     Tcl_AppendElement(interp, aBuf);
     sprintf(aBuf, "to %f %f %f", to[0], to[1], to[2]);
     Tcl_AppendElement(interp, aBuf);

     sprintf(aBuf, "intensity %f", [light intensity]);
     Tcl_AppendElement(interp, aBuf);

     [light getConeAngle:&coneAngle coneDelta:&coneDelta beamDistribution:&beamDistribution];
     sprintf(aBuf, "coneAngle %f", coneAngle);
     Tcl_AppendElement(interp, aBuf);
     sprintf(aBuf, "coneDelta %f", coneDelta);
     Tcl_AppendElement(interp, aBuf);
     sprintf(aBuf, "beamDistribution %f", beamDistribution);
     Tcl_AppendElement(interp, aBuf);

     color = [light color];
     sprintf(aBuf, "color %f %f %f", 
	     NXRedComponent(color), NXGreenComponent(color), NXBlueComponent(color));
     Tcl_AppendElement(interp, aBuf);

     sprintf(aBuf, "isGlobal %d", (int)[light isGlobal]);
     Tcl_AppendElement(interp, aBuf);

     Tcl_AppendResult(interp, "}", (char *)NULL);
  }
  Tcl_AppendResult(interp, "}", (char *)NULL);
  return TCL_OK;
}


static int
cmd_addAmbientLight(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char       *my_args = "name intensity [pathToParent]";
  int	     num_args = 3;
  WW3DLight  *light;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if ((argc != num_args) && (argc != (num_args+1)))
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  light = [[[WW3DLight alloc] init] makeAmbientWithIntensity:(RtFloat)atof(argv[2])];
  [light setShapeName:argv[1]];
  if (argc == (num_args + 1)) // local, visible light
  {  if (![me addLocalLight:light usingPath:argv[num_args]])
     {  sprintf(errBuf, "unable to add local ambient light %s as a child of shape %s", argv[1], argv[num_args]); 
        Tcl_AppendResult(interp, errBuf, (char *)NULL);
        return TCL_ERROR;
     }
  }
  else  // regular global light
  {  [me addLight:light];
  }
  return TCL_OK;
}


static int
cmd_addPointLight(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char       *my_args = "name fromX fromY fromZ intensity [pathToParent]";
  int	     num_args = 6;
  RtPoint    from;
  WW3DLight  *light;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if ((argc != num_args) && (argc != (num_args+1)))
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  from[0] = (RtFloat)atof(argv[2]);
  from[1] = (RtFloat)atof(argv[3]);
  from[2] = (RtFloat)atof(argv[4]);
  light = [[[WW3DLight alloc] init] makePointFrom:from intensity:(RtFloat)atof(argv[4])];
  [light setShapeName:argv[1]];
  if (argc == (num_args + 1)) // local, visible light
  {  if (![me addLocalLight:light usingPath:argv[num_args]])
     {  sprintf(errBuf, "unable to add local point light %s as a child of shape %s", argv[1], argv[num_args]); 
        Tcl_AppendResult(interp, errBuf, (char *)NULL);
        return TCL_ERROR;
     }
  }
  else  // regular global light
  {  [me addLight:light];
  }

  return TCL_OK;
}


static int
cmd_addDistantLight(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char       *my_args = "name fromX fromY fromZ toX toY toZ intensity [pathToParent]";
  int	     num_args = 9;
  RtPoint    from, to;
  WW3DLight  *light;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if ((argc != num_args) && (argc != (num_args+1)))
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  from[0] = (RtFloat)atof(argv[2]);
  from[1] = (RtFloat)atof(argv[3]);
  from[2] = (RtFloat)atof(argv[4]);
  to[0] = (RtFloat)atof(argv[5]);
  to[1] = (RtFloat)atof(argv[6]);
  to[2] = (RtFloat)atof(argv[7]);
  light = [[[WW3DLight alloc] init] makeDistantFrom:from to:to intensity:(RtFloat)atof(argv[8])];
  [light setShapeName:argv[1]];
  if (argc == (num_args + 1)) // local, visible light
  {  if (![me addLocalLight:light usingPath:argv[num_args]])
     {  sprintf(errBuf, "unable to add local distant light %s as a child of shape %s", argv[1], argv[num_args]); 
        Tcl_AppendResult(interp, errBuf, (char *)NULL);
        return TCL_ERROR;
     }
  }
  else  // regular global light
  {  [me addLight:light];
  }

  return TCL_OK;
}


static int
cmd_addSpotLight(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char       *my_args = "name fromX fromY fromZ toX toY toZ coneAngle coneDelta beamDistribution intensity [pathToParent]";
  int	     num_args = 12;
  RtPoint    from, to;
  WW3DLight  *light;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if ((argc != num_args) && (argc != (num_args+1)))
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  from[0] = (RtFloat)atof(argv[2]);
  from[1] = (RtFloat)atof(argv[3]);
  from[2] = (RtFloat)atof(argv[4]);
  to[0] = (RtFloat)atof(argv[5]);
  to[1] = (RtFloat)atof(argv[6]);
  to[2] = (RtFloat)atof(argv[7]);
  light = [[[WW3DLight alloc] init] makeSpotFrom:from to:to 
 	                                    coneAngle:(RtFloat)atof(argv[8]) 
	                                    coneDelta:(RtFloat)atof(argv[9]) 
	                                    beamDistribution:(RtFloat)atof(argv[10]) 
	                                    intensity:(RtFloat)atof(argv[11])];
  [light setShapeName:argv[1]];
  if (argc == (num_args + 1)) // local, visible light
  {  if (![me addLocalLight:light usingPath:argv[num_args]])
     {  sprintf(errBuf, "unable to add local point light %s as a child of shape %s", argv[1], argv[num_args]); 
        Tcl_AppendResult(interp, errBuf, (char *)NULL);
        return TCL_ERROR;
     }
  }
  else  // regular global light
  {  [me addLight:light];
  }

  return TCL_OK;
}


static int
cmd_setLightIntensity(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char       *my_args = "name intensity";
  int	     num_args = 3, i;
  WW3DLight  *light;
  id         lightList = [me lightList];
  id         otherLightList = [me otherLightList];


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  
  for (i = 0; i < [lightList count]; i++)
  {  light = [lightList objectAt:i];
     if (!strcmp(argv[1], [light shapeName]))
     {  [light setIntensity:(RtFloat)atof(argv[2])];
        return TCL_OK;
     }
  }

  for (i = 0; i < [otherLightList count]; i++)
  {  light = [otherLightList objectAt:i];
     if (!strcmp(argv[1], [light shapeName]))
     {  [light setIntensity:(RtFloat)atof(argv[2])];
        return TCL_OK;
     }
  }

  sprintf(errBuf, "no light named %s - unable to set intensity...", argv[1]);
  Tcl_AppendResult(interp, errBuf, (char *)NULL);
  return TCL_ERROR;
}


static int
cmd_setLightType(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char          *my_args = "name type";
  int	        num_args = 3, i;
  WW3DLight     *light;
  id            lightList = [me lightList];
  id         otherLightList = [me otherLightList];
  N3DLightType  lightType = N3D_AmbientLight;
  BOOL          validLightType = NO;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  if (!strcmp(argv[2], "ambient"))
  {  lightType = N3D_AmbientLight;
     validLightType = YES;
  }
  if (!strcmp(argv[2], "point"))
  {  lightType = N3D_PointLight;
     validLightType = YES;
  }
  if (!strcmp(argv[2], "distant"))
  {  lightType = N3D_DistantLight;
     validLightType = YES;
  }
  if (!strcmp(argv[2], "spot"))
  {  lightType = N3D_SpotLight;
     validLightType = YES;
  }
  if (!validLightType)
  {  sprintf(errBuf, "%s is not a valid light type (ambient|point|distant|spot)", argv[2]);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  for (i = 0; i < [lightList count]; i++)
  {  light = [lightList objectAt:i];
     if (!strcmp(argv[1], [light shapeName]))
     {  [light setType:lightType];
        return TCL_OK;
     }
  }

  for (i = 0; i < [otherLightList count]; i++)
  {  light = [otherLightList objectAt:i];
     if (!strcmp(argv[1], [light shapeName]))
     {  [light setType:lightType];
        return TCL_OK;
     }
  }

  sprintf(errBuf, "no light named %s - unable to set type...", argv[1]);
  Tcl_AppendResult(interp, errBuf, (char *)NULL);
  return TCL_ERROR;
}


static int
cmd_setLightFrom(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char       *my_args = "name fromX fromY fromZ";
  int	     num_args = 5, i;
  RtPoint    from;
  WW3DLight  *light;
  id         lightList = [me lightList];
  id         otherLightList = [me otherLightList];


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  from[0] = (RtFloat)atof(argv[2]);
  from[1] = (RtFloat)atof(argv[3]);
  from[2] = (RtFloat)atof(argv[4]);

  for (i = 0; i < [lightList count]; i++)
  {  light = [lightList objectAt:i];
     if (!strcmp(argv[1], [light shapeName]))
     {  [light setFrom:from];
        return TCL_OK;
     }
  }
  for (i = 0; i < [otherLightList count]; i++)
  {  light = [otherLightList objectAt:i];
     if (!strcmp(argv[1], [light shapeName]))
     {  [light setFrom:from];
        return TCL_OK;
     }
  }

  sprintf(errBuf, "no light named %s - unable to set from point...", argv[1]);
  Tcl_AppendResult(interp, errBuf, (char *)NULL);
  return TCL_ERROR;
}


static int
cmd_setLightTo(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char       *my_args = "name toX toY toZ";
  int	     num_args = 5, i;
  RtPoint    from, to;
  WW3DLight  *light;
  id         lightList = [me lightList];
  id         otherLightList = [me otherLightList];


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  for (i = 0; i < [lightList count]; i++)
  {  light = [lightList objectAt:i];
     if (!strcmp(argv[1], [light shapeName]))
     {  [light getFrom:&from to:&to];
        to[0] = (RtFloat)atof(argv[2]);
        to[1] = (RtFloat)atof(argv[3]);
        to[2] = (RtFloat)atof(argv[4]);
        [light setFrom:from to:to];
        return TCL_OK;
     }
  }

  for (i = 0; i < [otherLightList count]; i++)
  {  light = [otherLightList objectAt:i];
     if (!strcmp(argv[1], [light shapeName]))
     {  [light getFrom:&from to:&to];
        to[0] = (RtFloat)atof(argv[2]);
        to[1] = (RtFloat)atof(argv[3]);
        to[2] = (RtFloat)atof(argv[4]);
        [light setFrom:from to:to];
        return TCL_OK;
     }
  }

  sprintf(errBuf, "no light named %s - unable to set to point...", argv[1]);
  Tcl_AppendResult(interp, errBuf, (char *)NULL);
  return TCL_ERROR;
}


static int
cmd_setLightColor(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char       *my_args = "name r g b";
  int	     num_args = 5, i;
  NXColor    color;
  WW3DLight  *light;
  id         lightList = [me lightList];
  id         otherLightList = [me otherLightList];


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  color = NXConvertRGBToColor((float)atof(argv[2]), (float)atof(argv[3]), (float)atof(argv[4]));
  for (i = 0; i < [lightList count]; i++)
  {  light = [lightList objectAt:i];
     if (!strcmp(argv[1], [light shapeName]))
     {  [light setColor:color];
        return TCL_OK;
     }
  }

  for (i = 0; i < [otherLightList count]; i++)
  {  light = [otherLightList objectAt:i];
     if (!strcmp(argv[1], [light shapeName]))
     {  [light setColor:color];
        return TCL_OK;
     }
  }

  sprintf(errBuf, "no light named %s - unable to set color...", argv[1]);
  Tcl_AppendResult(interp, errBuf, (char *)NULL);
  return TCL_ERROR;
}


static int
cmd_setLightConeAngleConeDeltaBeamDistribution(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char       *my_args = "name coneAngle coneDelta beamDistribution";
  int	     num_args = 5, i;
  RtFloat    coneAngle, coneDelta, beamDistribution;
  WW3DLight  *light;
  id         lightList = [me lightList];
  id         otherLightList = [me otherLightList];


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  
  for (i = 0; i < [lightList count]; i++)
  {  light = [lightList objectAt:i];
     if (!strcmp(argv[1], [light shapeName]))
     {  coneAngle = (RtFloat)atof(argv[2]);
        coneDelta = (RtFloat)atof(argv[3]);
        beamDistribution = (RtFloat)atof(argv[4]);
        [light setConeAngle:coneAngle coneDelta:coneDelta beamDistribution:beamDistribution];
        return TCL_OK;
     }
  }

  for (i = 0; i < [otherLightList count]; i++)
  {  light = [otherLightList objectAt:i];
     if (!strcmp(argv[1], [light shapeName]))
     {  coneAngle = (RtFloat)atof(argv[2]);
        coneDelta = (RtFloat)atof(argv[3]);
        beamDistribution = (RtFloat)atof(argv[4]);
        [light setConeAngle:coneAngle coneDelta:coneDelta beamDistribution:beamDistribution];
        return TCL_OK;
     }
  }

  sprintf(errBuf, "no light named %s - unable to set coneAngle, coneDelta, beamDistribution...", argv[1]);
  Tcl_AppendResult(interp, errBuf, (char *)NULL);
  return TCL_ERROR;
}


static int
cmd_setLightConeAngle(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char       *my_args = "name coneAngle";
  int	     num_args = 3, i;
  RtFloat    coneAngle, coneDelta, beamDistribution;
  WW3DLight  *light;
  id         lightList = [me lightList];
  id         otherLightList = [me otherLightList];


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  
  for (i = 0; i < [lightList count]; i++)
  {  light = [lightList objectAt:i];
     if (!strcmp(argv[1], [light shapeName]))
     {  [light getConeAngle:&coneAngle coneDelta:&coneDelta beamDistribution:&beamDistribution];
        coneAngle = (RtFloat)atof(argv[2]);
        [light setConeAngle:coneAngle coneDelta:coneDelta beamDistribution:beamDistribution];
        return TCL_OK;
     }
  }

  for (i = 0; i < [otherLightList count]; i++)
  {  light = [otherLightList objectAt:i];
     if (!strcmp(argv[1], [light shapeName]))
     {  [light getConeAngle:&coneAngle coneDelta:&coneDelta beamDistribution:&beamDistribution];
        coneAngle = (RtFloat)atof(argv[2]);
        [light setConeAngle:coneAngle coneDelta:coneDelta beamDistribution:beamDistribution];
        return TCL_OK;
     }
  }

  sprintf(errBuf, "no light named %s - unable to set coneAngle...", argv[1]);
  Tcl_AppendResult(interp, errBuf, (char *)NULL);
  return TCL_ERROR;
}


static int
cmd_setLightConeDelta(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char       *my_args = "name coneDelta";
  int	     num_args = 3, i;
  RtFloat    coneAngle, coneDelta, beamDistribution;
  WW3DLight  *light;
  id         lightList = [me lightList];
  id         otherLightList = [me otherLightList];


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  
  for (i = 0; i < [lightList count]; i++)
  {  light = [lightList objectAt:i];
     if (!strcmp(argv[1], [light shapeName]))
     {  [light getConeAngle:&coneAngle coneDelta:&coneDelta beamDistribution:&beamDistribution];
        coneDelta = (RtFloat)atof(argv[2]);
        [light setConeAngle:coneAngle coneDelta:coneDelta beamDistribution:beamDistribution];
        return TCL_OK;
     }
  }

  for (i = 0; i < [otherLightList count]; i++)
  {  light = [otherLightList objectAt:i];
     if (!strcmp(argv[1], [light shapeName]))
     {  [light getConeAngle:&coneAngle coneDelta:&coneDelta beamDistribution:&beamDistribution];
        coneDelta = (RtFloat)atof(argv[2]);
        [light setConeAngle:coneAngle coneDelta:coneDelta beamDistribution:beamDistribution];
        return TCL_OK;
     }
  }

  sprintf(errBuf, "no light named %s - unable to set coneDelta...", argv[1]);
  Tcl_AppendResult(interp, errBuf, (char *)NULL);
  return TCL_ERROR;
}


static int
cmd_setLightBeamDistribution(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char       *my_args = "name beamDistribution";
  int	     num_args = 3, i;
  RtFloat    coneAngle, coneDelta, beamDistribution;
  WW3DLight  *light;
  id         lightList = [me lightList];
  id         otherLightList = [me otherLightList];


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  
  for (i = 0; i < [lightList count]; i++)
  {  light = [lightList objectAt:i];
     if (!strcmp(argv[1], [light shapeName]))
     {  [light getConeAngle:&coneAngle coneDelta:&coneDelta beamDistribution:&beamDistribution];
        beamDistribution = (RtFloat)atof(argv[2]);
        [light setConeAngle:coneAngle coneDelta:coneDelta beamDistribution:beamDistribution];
        return TCL_OK;
     }
  }

  for (i = 0; i < [otherLightList count]; i++)
  {  light = [otherLightList objectAt:i];
     if (!strcmp(argv[1], [light shapeName]))
     {  [light getConeAngle:&coneAngle coneDelta:&coneDelta beamDistribution:&beamDistribution];
        beamDistribution = (RtFloat)atof(argv[2]);
        [light setConeAngle:coneAngle coneDelta:coneDelta beamDistribution:beamDistribution];
        return TCL_OK;
     }
  }

  sprintf(errBuf, "no light named %s - unable to set beamDistribution...", argv[1]);
  Tcl_AppendResult(interp, errBuf, (char *)NULL);
  return TCL_ERROR;
}


static int
cmd_removeLight(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  int	        i;


  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  for (i = 1; i < argc; i++)
  {  if (![me removeLightNamed:argv[i]])
     {  sprintf(errBuf, "no light named %s - no further light removal done...", argv[i]);
        Tcl_AppendResult(interp, errBuf, (char *)NULL);
        return TCL_ERROR;
     }
  }

  return TCL_OK;
}


static int
cmd_rotateAroundY(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char       *my_args = "u", **argv2, *str;
  int	     num_args = 2, argc2;
  RtPoint    originalView;
  RtPoint    originalEye, newEye;
  double     u, radius, radiusSq;
  RtFloat    originalRollAngle;


  //sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  //[[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();

  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  // u varies from 0 to 1, and describes a perfect circle around the original look at point.
  // we want to stay at the same Y point, and move around in a circle, such that the radius
  // of the circle we're describing stays constant.  
  // we just want to set a new eye point and then reset the viewpoint to be the same as it had been.
  u = (float)atof(argv[1]);
  
  // need to grab "startCam(eyePoint)", which is a list
  str = [[me tclInterp] getVar2:"startCam" :"eyePoint"];
  if (!str)
  {  sprintf(errBuf, "startCam(viewPoint) is no defined - unable to interpolate");
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  Tcl_SplitList(interp, str, &argc2, &argv2);
  if (argc2 != 3)
  {  sprintf(errBuf, "startCam(eyePoint) is supposed to have 3 elements, not %d", argc2);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  N3D_XComp(originalEye) = (float)atof(argv2[0]);
  N3D_YComp(originalEye) = (float)atof(argv2[1]);
  N3D_ZComp(originalEye) = (float)atof(argv2[2]);
 
  // need to grab "startCam(viewPoint)", which is a list
  str = [[me tclInterp] getVar2:"startCam" :"viewPoint"];
  if (!str)
  {  sprintf(errBuf, "startCam(viewPoint) is no defined - unable to interpolate");
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  Tcl_SplitList(interp, str, &argc2, &argv2);
  if (argc2 != 3)
  {  sprintf(errBuf, "startCam(viewPoint) is supposed to have 3 elements, not %d", argc2);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }
  N3D_XComp(originalView) = (float)atof(argv2[0]);
  N3D_YComp(originalView) = (float)atof(argv2[1]);
  N3D_ZComp(originalView) = (float)atof(argv2[2]);
 
  radiusSq =   ((N3D_XComp(originalEye) - N3D_XComp(originalView)) * (N3D_XComp(originalEye) - N3D_XComp(originalView)))
             + ((N3D_ZComp(originalEye) - N3D_ZComp(originalView)) * (N3D_ZComp(originalEye) - N3D_ZComp(originalView)));
  radius = sqrt(radiusSq);
  NXLogError("radius == %f\n", radius);
  N3D_XComp(newEye) = N3D_XComp(originalView) + ((N3D_XComp(originalEye) - N3D_XComp(originalView)) * cos(2 * PI * u));
  N3D_YComp(newEye) = N3D_YComp(originalEye);
  N3D_ZComp(newEye) = N3D_ZComp(originalView) + ((N3D_ZComp(originalEye) - N3D_ZComp(originalView)) * sin(2 * PI * u));

  // need to grab "startCam(rollAngle)"
  str = [[me tclInterp] getVar2:"startCam" :"rollAngle"];
  originalRollAngle = (RtFloat)atof(str);

  [me setEyeAt:newEye toward:originalView roll:originalRollAngle];

  return TCL_OK;
}

static int
cmd_noop(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  sprintf(errBuf, "WW3DCamera's interp evaluating %s...", argv[0]);
  [[me statusText] setStringValue:errBuf]; [[me statusText] display]; NXPing();
  return TCL_OK;
}

#if 0
static int
cmd_(me, interp, argc, argv)
WW3DCamera *me;
Tcl_Interp *interp;
int argc;
char **argv;
{
  char  *my_args = "";
  int	num_args = 1;


  if (argc != num_args)
  {  sprintf(errBuf, "USAGE: %s %s", argv[0], my_args);
     Tcl_AppendResult(interp, errBuf, (char *)NULL);
     return TCL_ERROR;
  }

  return TCL_OK;
}
#endif

- initInterp
{
  [tclInterp addCommand:"dumpRIBToFile" :cmd_dumpRIBToFile :self];
  [tclInterp addCommand:"display" :cmd_display :self];
  [tclInterp addCommand:"renderStyle" :cmd_renderStyle :self];
  [tclInterp addCommand:"movingRenderStyle" :cmd_movingRenderStyle :self];
  [tclInterp addCommand:"tesselation" :cmd_tesselation :self];
  [tclInterp addCommand:"lowRezTesselation" :cmd_lowRezTesselation :self];
  [tclInterp addCommand:"backgroundColor" :cmd_backgroundColor :self];
  [tclInterp addCommand:"fieldOfView" :cmd_fieldOfView :self];
  [tclInterp addCommand:"setEyeAt:toward:roll:" :cmd_setEyeAtTowardRoll :self];
  [tclInterp addCommand:"setEyeAtTowardRoll" :cmd_setEyeAtTowardRoll :self];
  [tclInterp addCommand:"moveEyeBy:::" :cmd_moveEyeBy :self];
  [tclInterp addCommand:"moveEyeBy" :cmd_moveEyeBy :self];
  [tclInterp addCommand:"setSurfaceTypeForAll" :cmd_setSurfaceTypeForAll :self];
  [tclInterp addCommand:"getEyePoint" :cmd_getEyePoint :self];
  [tclInterp addCommand:"getViewPoint" :cmd_getViewPoint :self];
  [tclInterp addCommand:"getRollAngle" :cmd_getRollAngle :self];
  [tclInterp addCommand:"getEyeAt:toward:roll:" :cmd_getEyeAtTowardRoll :self];
  [tclInterp addCommand:"getEyeAtTowardRoll" :cmd_getEyeAtTowardRoll :self];
  [tclInterp addCommand:"removeDefaultLights" :cmd_removeDefaultLights :self];
  [tclInterp addCommand:"restoreDefaultLights" :cmd_restoreDefaultLights :self];
  [tclInterp addCommand:"lightList" :cmd_lightList :self];
  [tclInterp addCommand:"otherLightList" :cmd_otherLightList :self];
  [tclInterp addCommand:"localLightList" :cmd_otherLightList :self];
  [tclInterp addCommand:"addAmbientLight" :cmd_addAmbientLight :self];
  [tclInterp addCommand:"addPointLight" :cmd_addPointLight :self];
  [tclInterp addCommand:"addDistantLight" :cmd_addDistantLight :self];
  [tclInterp addCommand:"addSpotLight" :cmd_addSpotLight :self];
  [tclInterp addCommand:"setLightIntensity" :cmd_setLightIntensity :self];
  [tclInterp addCommand:"setLightType" :cmd_setLightType :self];
  [tclInterp addCommand:"setLightFrom" :cmd_setLightFrom :self];
  [tclInterp addCommand:"setLightTo" :cmd_setLightTo :self];
  [tclInterp addCommand:"setLightColor" :cmd_setLightColor :self];
  [tclInterp addCommand:"setLightConeAngleConeDeltaBeamDistribution" :cmd_setLightConeAngleConeDeltaBeamDistribution :self];
  [tclInterp addCommand:"setLightConeAngle" :cmd_setLightConeAngle :self];
  [tclInterp addCommand:"setLightConeDelta" :cmd_setLightConeDelta :self];
  [tclInterp addCommand:"setLightBeamDistribution" :cmd_setLightBeamDistribution :self];
  [tclInterp addCommand:"removeLight" :cmd_removeLight :self];

  [tclInterp addCommand:"rotateAroundY" :cmd_rotateAroundY :self];

  [tclInterp addCommand:"askModel" :cmd_noop :self];
  [tclInterp addCommand:"noop" :cmd_noop :self];

  return self;
}

- setupDefaultLights
{
   RtPoint lFromP = {0.5,0.5,-0.75};
   RtPoint lToP = {0, 0, 0};

   // ambient light
   ambientLight=[[WW3DLight alloc] init];
   [ambientLight setShapeName:"ambientLight"];
   [ambientLight makeAmbientWithIntensity:0.3];
   [self addLight:ambientLight];

   // distant light
   rightLight=[[WW3DLight alloc] init];
   [rightLight setShapeName:"rightDistantLight"];
   [rightLight makeDistantFrom:lFromP to:lToP intensity:0.7];
   [self addLight:rightLight];

  // point light
  //N3D_XComp( lFromP ) = -0.5;
  //N3D_YComp( lFromP ) = -0.5;
  //leftLight=[[WW3DLight alloc] init];
  //[leftLight setShapeName:"leftPointLight"];
  //[leftLight makePointFrom:lFromP intensity:0.7];
  //[self addLight:leftLight];

  defaultLightsInUse = YES;

  return self;
}

- restoreDefaultLights
{
  if (!defaultLightsInUse)
  {  [self addLight:ambientLight];
     [self addLight:rightLight];
     //[self addLight:leftLight];
     defaultLightsInUse = YES;
  }
  return self;
}

- removeDefaultLights
{
  if (defaultLightsInUse)
  {  [self removeLight:ambientLight];
     [self removeLight:rightLight];
     [self removeLight:leftLight];
     defaultLightsInUse = NO;
  }
  return self;
}


- removeLightNamed:(const char *)lightName
{
   int  i;
   id   light;


   for (i = 0; i < [lightList count]; i++)
   {  light = [lightList objectAt:i];
      if (!strcmp(lightName, [light shapeName]))
      {  [self removeLight:light];
         return self;
      }
   }
   return nil;
}


- setFieldOfViewByAngle:(float)viewAngle
{
  char   buf[128];


  [super setFieldOfViewByAngle:viewAngle];
  sprintf(buf, "%f", viewAngle);
  [tclInterp setVar2:"cam" :"fieldOfView" toValue:buf];
  [delegate cameraParametersWereUpdated:self];

  return self;
}


char *ReadOnlyWriteProc(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  return "this is a read-only variable";
}


char *WriteProcForInterpolationProc(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  // when a new interpolation proc is defined, there are several things we want to do.
  // first of all, we want to make sure that it's valid:
  // - a valid interpolation proc only has one argument: u

  // the other thing we might want to do is update the inspector panel (if we have one) about the contents of this guy...

  return NULL;
}


// someone is writing a new value for the cam(movingStyle) parameter, so we need
// to update the WW3DCamera to reflect that new value...
char *WriteProcForRenderStyle(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   *varString;
  id     me;


  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];
  me = [(WWTCLVarTrace *)clientData datum];
  if (varString)
  {  if (!strcmp(varString, "PointCloud"))
     {  [me setRenderStyle:N3D_PointCloud];
        return NULL;
     }
     if (!strcmp(varString, "WireFrame"))
     {  [me setRenderStyle:N3D_WireFrame];
        return NULL;
     }
     if (!strcmp(varString, "ShadedWireFrame"))
     {  [me setRenderStyle:N3D_ShadedWireFrame];
        return NULL;
     }
     if (!strcmp(varString, "FacetedSolids"))
     {  [me setRenderStyle:N3D_FacetedSolids];
        return NULL;
     }
     if (!strcmp(varString, "SmoothSolids"))
     {  [me setRenderStyle:N3D_SmoothSolids];
        return NULL;
     }
  }
  return "invalid value for \"cam(renderStyle)\"; must be on of PointCloud|WireFrame|ShadedWireFrame|FacetedSolids|SmoothSolids";
}


// someone is reading a value for the cam(renderStyle) parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForRenderStyle(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  int    renderStyle;
  id     me;


  renderStyle = [[(WWTCLVarTrace *)clientData datum] renderStyle];
  me = [(WWTCLVarTrace *)clientData datum];
  if (renderStyle == N3D_PointCloud)
  {  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:"PointCloud"];
     return NULL;
  }
  if (renderStyle == N3D_WireFrame)
  {  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:"WireFrame"];
     return NULL;
  }
  if (renderStyle == N3D_ShadedWireFrame)
  {  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:"ShadedWireFrame"];
     return NULL;
  }
  if (renderStyle == N3D_FacetedSolids)
  {  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:"FacetedSolids"];
     return NULL;
  }
  if (renderStyle == N3D_SmoothSolids)
  {  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:"SmoothSolids"];
     return NULL;
  }
  
  return NULL;
}

// someone is writing a new value for the cam(movingStyle) parameter, so we need
// to update the WW3DCamera to reflect that new value...
char *WriteProcForMovingRenderStyle(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   *varString;
  id     me;


  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];
  me = [(WWTCLVarTrace *)clientData datum];
  if (varString)
  {  if (!strcmp(varString, "PointCloud"))
     {  [me setMovingRenderStyle:N3D_PointCloud];
        return NULL;
     }
     if (!strcmp(varString, "WireFrame"))
     {  [me setMovingRenderStyle:N3D_WireFrame];
        return NULL;
     }
     if (!strcmp(varString, "ShadedWireFrame"))
     {  [me setMovingRenderStyle:N3D_ShadedWireFrame];
        return NULL;
     }
     if (!strcmp(varString, "FacetedSolids"))
     {  [me setMovingRenderStyle:N3D_FacetedSolids];
        return NULL;
     }
     if (!strcmp(varString, "SmoothSolids"))
     {  [me setMovingRenderStyle:N3D_SmoothSolids];
        return NULL;
     }
  }
  return "invalid value for \"cam(movingRenderStyle)\"; must be on of PointCloud|WireFrame|ShadedWireFrame|FacetedSolids|SmoothSolids";
}


// someone is reading a value for the cam(MovingRenderStyle) parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForMovingRenderStyle(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  int    movingRenderStyle;


  movingRenderStyle = [[(WWTCLVarTrace *)clientData datum] movingRenderStyle];
  if (movingRenderStyle == N3D_PointCloud)
  {  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:"PointCloud"];
     return NULL;
  }
  if (movingRenderStyle == N3D_WireFrame)
  {  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:"WireFrame"];
     return NULL;
  }
  if (movingRenderStyle == N3D_ShadedWireFrame)
  {  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:"ShadedWireFrame"];
     return NULL;
  }
  if (movingRenderStyle == N3D_FacetedSolids)
  {  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:"FacetedSolids"];
     return NULL;
  }
  if (movingRenderStyle == N3D_SmoothSolids)
  {  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:"SmoothSolids"];
     return NULL;
  }
  
  return NULL;
}

// someone is writing a new value for the cam(tesselation) parameter, so we need
// to update the WW3DCamera to reflect that new value...
char *WriteProcForTesselation(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   *varString;
  float  tesselation = 32.;


  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];
  sscanf(varString, "%f", &tesselation);
  [[(WWTCLVarTrace *)clientData datum] setTesselation:tesselation];

  return NULL;
}


// someone is reading a value for the cam(tesselation) parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForTesselation(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   buf[128];
  float  tesselation;


  tesselation = [[(WWTCLVarTrace *)clientData datum] tesselation];
  sprintf(buf, "%f", tesselation);
  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:buf];

  return NULL;
}

// someone is writing a new value for the cam(lowRezTesselation) parameter, so we need
// to update the WW3DCamera to reflect that new value...
char *WriteProcForLowRezTesselation(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   *varString;
  float  lowRezTesselation = 16.;


  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];
  sscanf(varString, "%f", &lowRezTesselation);
  [[(WWTCLVarTrace *)clientData datum] setLowRezTesselation:lowRezTesselation];

  return NULL;
}


// someone is reading a value for the cam(tesselation) parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForLowRezTesselation(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   buf[128];
  float  lowRezTesselation;


  lowRezTesselation = [[(WWTCLVarTrace *)clientData datum] lowRezTesselation];
  sprintf(buf, "%f", lowRezTesselation);
  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:buf];

  return NULL;
}

// someone is writing a new value for the cam(fieldOfView) parameter, so we need
// to update the WW3DCamera to reflect that new value...

// really should constrain fov to be from <0 && <180
char *WriteProcForFieldOfViewByAngle(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   *varString;
  float  fieldOfView = 45.;


  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];
  sscanf(varString, "%f", &fieldOfView);
  [[(WWTCLVarTrace *)clientData datum] setFieldOfViewByAngle:fieldOfView];

  return NULL;
}


// someone is reading a value for the cam(fieldOfView) parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForFieldOfViewByAngle(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   buf[128];
  float  fieldOfView;


  fieldOfView = [[(WWTCLVarTrace *)clientData datum] fieldOfView];
  sprintf(buf, "%f", fieldOfView);
  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:buf];

  return NULL;
}

- setEyeAt:(RtPoint)fromPoint toward:(RtPoint)toPoint roll:(float)aRollAngle;
{
  char   buf[384];


  [super setEyeAt:fromPoint toward:toPoint roll:aRollAngle];

  sprintf(buf, "%f %f %f", N3D_XComp(fromPoint), N3D_YComp(fromPoint), N3D_ZComp(fromPoint));
  [tclInterp setVar2:"cam" :"eyePoint" toValue:buf];
  sprintf(buf, "%f %f %f", N3D_XComp(toPoint), N3D_YComp(toPoint), N3D_ZComp(toPoint));
  [tclInterp setVar2:"cam" :"viewPoint" toValue:buf];
  sprintf(buf, "%f", aRollAngle);
  [tclInterp setVar2:"cam" :"rollAngle" toValue:buf];

  [delegate cameraParametersWereUpdated:self];
  return self;
}

// someone is writing a new value for the cam(eyePoint) parameter, so we need
// to update the WW3DCamera to reflect that new value...
char *WriteProcForEyePoint(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char     *varString, **argv;
  int      argc;
  RtPoint  anEyePoint, aViewPoint;
  RtFloat  aRollAngle;
 

  // get current values
  [[(WWTCLVarTrace *)clientData datum] getEyeAt:&anEyePoint toward:&aViewPoint roll:&aRollAngle];
  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];
  Tcl_SplitList(interp, varString, &argc, &argv); // fromPoint
  if (argc != 3)
  {  NXLogError("cam(eyePoint) should be a tcl list with 3 components, not <%s>\n", varString);
     if (argv) { free(argv); }
     return NULL;
  }
  N3D_XComp(anEyePoint) = (float)atof(argv[0]);
  N3D_YComp(anEyePoint) = (float)atof(argv[1]);
  N3D_ZComp(anEyePoint) = (float)atof(argv[2]);
  free(argv);

  [[(WWTCLVarTrace *)clientData datum] setEyeAt:anEyePoint toward:aViewPoint roll:aRollAngle];

  return NULL;
}


// someone is reading a value for the cam() parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForEyePoint(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char     buf[384];
  RtPoint  anEyePoint, aViewPoint;
  RtFloat  aRollAngle;
 

  // get current values
  [[(WWTCLVarTrace *)clientData datum] getEyeAt:&anEyePoint toward:&aViewPoint roll:&aRollAngle];

  sprintf(buf, "%f %f %f", N3D_XComp(anEyePoint), N3D_YComp(anEyePoint), N3D_ZComp(anEyePoint));
  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:buf];

  return NULL;
}

// someone is writing a new value for the cam(viewPoint) parameter, so we need
// to update the WW3DCamera to reflect that new value...
char *WriteProcForViewPoint(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char     *varString, **argv;
  int      argc;
  RtPoint  anEyePoint, aViewPoint;
  RtFloat  aRollAngle;
 

  // get current values
  [[(WWTCLVarTrace *)clientData datum] getEyeAt:&anEyePoint toward:&aViewPoint roll:&aRollAngle];
  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];
  Tcl_SplitList(interp, varString, &argc, &argv); // viewPoint
  if (argc != 3)
  {  NXLogError("cam(viewPoint) should be a tcl list with 3 components, not <%s>\n", varString);
     if (argv) { free(argv); }
     return NULL;
  }
  N3D_XComp(aViewPoint) = (float)atof(argv[0]);
  N3D_YComp(aViewPoint) = (float)atof(argv[1]);
  N3D_ZComp(aViewPoint) = (float)atof(argv[2]);
  free(argv);

  [[(WWTCLVarTrace *)clientData datum] setEyeAt:anEyePoint toward:aViewPoint roll:aRollAngle];

  return NULL;
}


// someone is reading a value for the cam(viewPoint) parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForViewPoint(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char     buf[384];
  RtPoint  anEyePoint, aViewPoint;
  RtFloat  aRollAngle;
 

  // get current values
  [[(WWTCLVarTrace *)clientData datum] getEyeAt:&anEyePoint toward:&aViewPoint roll:&aRollAngle];

  sprintf(buf, "%f %f %f", N3D_XComp(aViewPoint), N3D_YComp(aViewPoint), N3D_ZComp(aViewPoint));
  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:buf];

  return NULL;
}

// someone is writing a new value for the cam(rollAngle) parameter, so we need
// to update the WW3DCamera to reflect that new value...
char *WriteProcForRollAngle(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char     *varString;
  RtPoint  anEyePoint, aViewPoint;
  RtFloat  aRollAngle;
 

  // get current values
  [[(WWTCLVarTrace *)clientData datum] getEyeAt:&anEyePoint toward:&aViewPoint roll:&aRollAngle];
  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];
  aRollAngle = (float)atof(varString);

  [[(WWTCLVarTrace *)clientData datum] setEyeAt:anEyePoint toward:aViewPoint roll:aRollAngle];

  return NULL;
}


// someone is reading a value for the cam(rollAngle) parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForRollAngle(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char     buf[384];
  RtPoint  anEyePoint, aViewPoint;
  RtFloat  aRollAngle;
 

  // get current values
  [[(WWTCLVarTrace *)clientData datum] getEyeAt:&anEyePoint toward:&aViewPoint roll:&aRollAngle];

  sprintf(buf, "%f", aRollAngle);
  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:buf];

  return NULL;
}


- (RtFloat)fStop { return fStop; }

- setFStop:(float)newFStop
{
  char   buf[128];


  fStop = newFStop;
  sprintf(buf, "%f", fStop);
  [tclInterp setVar2:"cam" :"fStop" toValue:buf];
  [delegate cameraParametersWereUpdated:self];

  return self;
}


// someone is writing a new value for the cam(fStop) parameter, so we need
// to update the WW3DCamera to reflect that new value...
char *WriteProcForFStop(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   *varString;
  float  fStop = 1.0;


  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];
  sscanf(varString, "%f", &fStop);
  [[(WWTCLVarTrace *)clientData datum] setFStop:fStop];

  return NULL;
}


// someone is reading a value for the cam(fStop) parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForFStop(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   buf[128];
  float  fStop;


  fStop = [[(WWTCLVarTrace *)clientData datum] fStop];
  sprintf(buf, "%f", fStop);
  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:buf];

  return NULL;
}

- (RtFloat)focalLength { return focalLength; }

- setFocalLength:(float)newFocalLength
{
  char   buf[128];


  focalLength = newFocalLength;
  sprintf(buf, "%f", focalLength);
  [tclInterp setVar2:"cam" :"focalLength" toValue:buf];

  [delegate cameraParametersWereUpdated:self];

  return self;
}


// someone is writing a new value for the cam(fStop) parameter, so we need
// to update the WW3DCamera to reflect that new value...
char *WriteProcForFocalLength(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   *varString;
  float  focalLength = 1.0;


  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];
  sscanf(varString, "%f", &focalLength);
  [[(WWTCLVarTrace *)clientData datum] setFocalLength:focalLength];

  return NULL;
}


// someone is reading a value for the cam(fStop) parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForFocalLength(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   buf[128];
  float  focalLength;


  focalLength = [[(WWTCLVarTrace *)clientData datum] focalLength];
  sprintf(buf, "%f", focalLength);
  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:buf];

  return NULL;
}


- (RtFloat)focalDistance { return focalDistance; }

- setFocalDistance:(float)newFocalDistance
{
  char   buf[128];


  if (newFocalDistance > 0)
  {  focalDistance = newFocalDistance;
     sprintf(buf, "%f", focalDistance);
     [tclInterp setVar2:"cam" :"focalDistance" toValue:buf];

     [delegate cameraParametersWereUpdated:self];
  }

  return self;
}


// someone is writing a new value for the cam(focalDistance) parameter, so we need
// to update the WW3DCamera to reflect that new value...
char *WriteProcForFocalDistance(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   *varString;
  float  focalDistance = 1.0;


  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];
  sscanf(varString, "%f", &focalDistance);
  [[(WWTCLVarTrace *)clientData datum] setFocalDistance:focalDistance];

  return NULL;
}


// someone is reading a value for the cam(focalDistance) parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForFocalDistance(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   buf[128];
  float  focalDistance;


  focalDistance = [[(WWTCLVarTrace *)clientData datum] focalDistance];
  sprintf(buf, "%f", focalDistance);
  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:buf];

  return NULL;
}

- setFrameNumber:(int)aFrameNumber
{
  char   buf[128];


  if ((aFrameNumber <= [self endFrame]) && ([self frameNumber] != aFrameNumber))  //it's valid and it's new
  {  // if it's actually a new frame, we need to flip the "evaluateInterpProc"
     // flag, so that the next time we render, we run the camera interpolation routine...
     evaluateInterpProc = YES;
  }

  // we assume that the [super setFrameNumber:]  clamps to endFrameNumber
  [super setFrameNumber:aFrameNumber];

  // by setting the frame number, we need to make sure that all the
  // appropriate other parts of the camera have been updated.  What
  // automatically follows along?  Well, the shutterOpenTime needs to get
  // updated, at least...
  [self setShutterOpenTime:(shotStartTime + (frameNumber * frameTimeIncrement))];

  sprintf(buf, "%d", frameNumber);
  [tclInterp setVar2:"cam" :"frameNumber" toValue:buf];
  [delegate cameraParametersWereUpdated:self];

  return self;
}


- resetFrameNumber
{
  [self setFrameNumber:0];
  return self;
}

// someone is writing a new value for the cam(frameNumber) parameter, so we need
// to update the WW3DCamera to reflect that new value...
char *WriteProcForFrameNumber(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   *varString;
  int    frameNumber = 1;


  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];

  // frameNumber has to be an integer


  sscanf(varString, "%d", &frameNumber);
  [[(WWTCLVarTrace *)clientData datum] setFrameNumber:frameNumber];

  return NULL;
}


// someone is reading a value for the cam(frameNumber) parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForFrameNumber(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   buf[128];
  int    frameNumber;


  frameNumber = [[(WWTCLVarTrace *)clientData datum] frameNumber];
  sprintf(buf, "%d", frameNumber);
  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:buf];

  return NULL;
}

// whenever the frame number or the frameTimeIncrement gets changed,
// the open and close time need to be recalculated

- (RtFloat)shutterOpenTime { return shutterOpenTime; }

- setShutterOpenTime:(RtFloat)newShutterOpenTime
{
  char   buf[128];


  shutterOpenTime = newShutterOpenTime;
  sprintf(buf, "%f", shutterOpenTime);
  [tclInterp setVar2:"cam" :"shutterOpenTime" toValue:buf];
  [delegate cameraParametersWereUpdated:self];

  return self;
}


// someone is writing a new value for the cam(shutterOpenTime) parameter, so we need
// to update the WW3DCamera to reflect that new value...
char *WriteProcForShutterOpenTime(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   *varString;
  RtFloat  shutterOpenTime = 0.0;


  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];
  sscanf(varString, "%f", &shutterOpenTime);
  [[(WWTCLVarTrace *)clientData datum] setShutterOpenTime:shutterOpenTime];

  return NULL;
}


// someone is reading a value for the cam(shutterOpenTime) parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForShutterOpenTime(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   buf[128];
  RtFloat  shutterOpenTime;


  shutterOpenTime = [[(WWTCLVarTrace *)clientData datum] shutterOpenTime];
  sprintf(buf, "%f", shutterOpenTime);
  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:buf];

  return NULL;
}

- (RtFloat)shutterCloseTime 
{ 
   if (shooting)
   {  return (shutterOpenTime + (exposureLength * exposureLengthFactor)); 
   }
   // it's a still frame; no need to modulate the exposure length...
   return (shutterOpenTime + exposureLength);
}

// someone is writing a new value for the cam(shutterCloseTime) parameter, so we need
// to update the WW3DCamera to reflect that new value...
char *WriteProcForShutterCloseTime(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  return "cam(shutterCloseTime) is read-only";
}


// someone is reading a value for the cam(shutterCloseTime) parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForShutterCloseTime(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   buf[128];
  RtFloat  shutterCloseTime;


  shutterCloseTime = [[(WWTCLVarTrace *)clientData datum] shutterCloseTime];
  sprintf(buf, "%f", shutterCloseTime);
  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:buf];

  return NULL;
}

- (float)frameTimeIncrement { return frameTimeIncrement; }

- (float)exposureLength { return exposureLength; }

- setExposureLength:(float)newExposureLength
{
  char   buf[128];

  // need to make sure that the new exposure length is consistent with the rest of the camera
  // what does this mean?
  // the length of the exposure cannot be longer than the interval between frames, i.e. 
  // exposureLength <= (frameTimeIncrement == 1./framesPerSecond)
  // 
  //NXLogError("newExposureLength == %f\n", newExposureLength);

  if (newExposureLength < 0.0)
  {  NXLogError("exposureLength must be a positive number, not %f\n", newExposureLength);
     return nil;
  }

  // actually, we only want to do this if we're shooting.  Anyway, at
  // the start of shooting a shot (i.e. an ordered sequence), we should
  // sanity check then - this might be perfectly reasonable for a still
  // frame...
  if (shooting)
  {  if (newExposureLength > [self frameTimeIncrement])
     {  exposureLength = [self frameTimeIncrement];
     }
     else
     {  exposureLength = newExposureLength;
     }
  }
  else  // don't do a clip; it could be whatever positive value they want...
  {  exposureLength = newExposureLength;
  }
  sprintf(buf, "%f", exposureLength);
  [tclInterp setVar2:"cam" :"exposureLength" toValue:buf];
  [delegate cameraParametersWereUpdated:self];
  justResetExposureLength = YES;


  return self;
}


// someone is writing a new value for the cam(exposureLength) parameter, so we need
// to update the WW3DCamera to reflect that new value...
char *WriteProcForExposureLength(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   *varString;
  RtFloat  exposureLength = 0.0;


  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];
  sscanf(varString, "%f", &exposureLength);
  [[(WWTCLVarTrace *)clientData datum] setExposureLength:exposureLength];

  return NULL;
}


// someone is reading a value for the cam(exposureLength) parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForExposureLength(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   buf[128];
  RtFloat  exposureLength;


  exposureLength = [[(WWTCLVarTrace *)clientData datum] exposureLength];
  sprintf(buf, "%f", exposureLength);
  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:buf];

  return NULL;
}

- (float)framesPerSecond { return framesPerSecond; }

- setFramesPerSecond:(float)newFramesPerSecond
{
  char     buf[128];
  RtFloat  maxExposureLength;
  int      newEndFrame;


  // okay, the frames/second is being changed.  
  // what do we need to update?
  // we want the duration of the shot (in seconds) to stay the same, 
  // so we need to:
  // - make sure that the exposureLength is still valid; if not, clamp it
  // - reset the startFrame to 0 and the endFrame to whatever is appropriate 

  framesPerSecond = newFramesPerSecond;
  sprintf(buf, "%f", newFramesPerSecond);
  [tclInterp setVar2:"cam" :"FramesPerSecond" toValue:buf];

  frameTimeIncrement = (1.0/framesPerSecond);
  maxExposureLength = frameTimeIncrement;

  // if, by changing the frame rate, we've invalidated the current
  // setting for how long the shutter stays open for each frame, we need to
  // reset it to a valid value.  If it's still valid, leave it be (i.e. if
  // it was a strobe camera, it will stay that way).
  // uh, no.  There are two normal cases: keep the exposureLength at 0.0, for a strobe,
  // or keep the exposureLength == frameTimeIncrement.  So... if they want a special
  // setting, they'll need to reset it after setting a new fps.
  // uh, no I changed my mind again.  If they want a *strobe*, they have to reset it explicitly
  // this is what I want in most cases (i.e. I want motion blur)
  [self setExposureLength:maxExposureLength];

  newEndFrame = (framesPerSecond * [self shotLength]); // actually should do the right thing at the half frame mark...
  [super setStartFrame:0 endFrame:newEndFrame incrementFramesBy:[self frameIncrement]];

  [delegate cameraParametersWereUpdated:self];

  return self;
}


// someone is writing a new value for the cam(framesPerSecond) parameter, so we need
// to update the WW3DCamera to reflect that new value...
char *WriteProcForFramesPerSecond(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   *varString;
  float  framesPerSecond = 17.0;


  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];

  // framesPerSecond has to be a float
  sscanf(varString, "%f", &framesPerSecond);
  [[(WWTCLVarTrace *)clientData datum] setFramesPerSecond:framesPerSecond];

  return NULL;
}


// someone is reading a value for the cam(framesPerSecond) parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForFramesPerSecond(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   buf[128];
  float  framesPerSecond;


  framesPerSecond = [[(WWTCLVarTrace *)clientData datum] framesPerSecond];
  sprintf(buf, "%f", framesPerSecond);
  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:buf];

  return NULL;
}

- (float)shotLength { return shotLength; }

- setShotLength:(float)newShotLength
{
  char     buf[128];
  int      newEndFrame;


  // okay, the frames/second is being changed.  
  // what do we need to update?
  // - reset the startFrame to 0 and the endFrame to whatever is appropriate 

  shotLength = newShotLength;
  sprintf(buf, "%f", newShotLength);
  [tclInterp setVar2:"cam" :"shotLength" toValue:buf];

  newEndFrame = (shotLength * [self framesPerSecond]) - 1; // actually should the right thing at the half frame mark...
  [super setStartFrame:0 endFrame:newEndFrame incrementFramesBy:[self frameIncrement]];

  [delegate cameraParametersWereUpdated:self];

  return self;
}


// someone is writing a new value for the cam(shotLength) parameter, so we need
// to update the WW3DCamera to reflect that new value...
char *WriteProcForShotLength(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   *varString;
  float  shotLength = 17.0;


  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];

  // ShotLength has to be a float
  sscanf(varString, "%f", &shotLength);
  [[(WWTCLVarTrace *)clientData datum] setShotLength:shotLength];

  return NULL;
}


// someone is reading a value for the cam(ShotLength) parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForShotLength(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   buf[128];
  float  shotLength;


  shotLength = [[(WWTCLVarTrace *)clientData datum] shotLength];
  sprintf(buf, "%f", shotLength);
  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:buf];

  return NULL;
}

- (float)shotStartTime { return shotStartTime; }

- setShotStartTime:(float)newShotStartTime
{
  char     buf[128];


  shotStartTime = newShotStartTime;
  sprintf(buf, "%f", newShotStartTime);
  [tclInterp setVar2:"cam" :"shotStartTime" toValue:buf];

  [delegate cameraParametersWereUpdated:self];

  return self;
}


// someone is writing a new value for the cam(shotStartTime) parameter, so we need
// to update the WW3DCamera to reflect that new value...
char *WriteProcForShotStartTime(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   *varString;
  float  shotStartTime = 0.0;


  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];

  // ShotStartTime has to be a float
  sscanf(varString, "%f", &shotStartTime);
  [[(WWTCLVarTrace *)clientData datum] setShotStartTime:shotStartTime];

  return NULL;
}


// someone is reading a value for the cam(shotStartTime) parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForShotStartTime(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   buf[128];
  float  shotStartTime;


  shotStartTime = [[(WWTCLVarTrace *)clientData datum] shotStartTime];
  sprintf(buf, "%f", shotStartTime);
  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:buf];

  return NULL;
}

- (float)exposureLengthFactor { return exposureLengthFactor; }

- setExposureLengthFactor:(float)newExposureLengthFactor
{
  char     buf[128];


  exposureLengthFactor = newExposureLengthFactor;
  // bound it inclusively between 0 and 1
  if (exposureLengthFactor < 0.0)
  {  exposureLengthFactor = 0.0;
  }
  if (exposureLengthFactor > 1.0)
  {  exposureLengthFactor = 1.0;
  }
  sprintf(buf, "%f", newExposureLengthFactor);
  [tclInterp setVar2:"cam" :"exposureLengthFactor" toValue:buf];

  [delegate cameraParametersWereUpdated:self];

  return self;
}


// someone is writing a new value for the cam(exposureLengthFactor) parameter, so we need
// to update the WW3DCamera to reflect that new value...
char *WriteProcForExposureLengthFactor(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   *varString;
  float  exposureLengthFactor = 0.0;


  varString = [[(WWTCLVarTrace *)clientData tclInterp] getVar2:name1 :name2];

  // exposureLengthFactor has to be a float
  sscanf(varString, "%f", &exposureLengthFactor);
  [[(WWTCLVarTrace *)clientData datum] setExposureLengthFactor:exposureLengthFactor];

  return NULL;
}


// someone is reading a value for the cam(exposureLengthFactor) parameter, so we need
// to " read-through" the WW3DCamera to reflect the current value...
char *ReadProcForExposureLengthFactor(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)
{
  char   buf[128];
  float  exposureLengthFactor;


  exposureLengthFactor = [[(WWTCLVarTrace *)clientData datum] exposureLengthFactor];
  sprintf(buf, "%f", exposureLengthFactor);
  [[(WWTCLVarTrace *)clientData tclInterp] setVar2:name1 :name2 toValue:buf];

  return NULL;
}

- (BOOL)shooting { return shooting; }

- setShooting:(BOOL)newShooting
{
  if (shooting && !newShooting)
  {  justFinishedShooting = YES;
  }
  shooting = newShooting;

  return self;
}

- takeShooting:sender
{
  [self setShooting:(BOOL)[sender intValue]];
  return self;
}

// in this routine, the tcl interp is assumed to have the correct values, and so the camera has to conform to it...
// cam(fieldOfView) : fieldOfView : float -> float
// cam(eyePoint) : eyePoint : RtPoint -> {x y z}
// cam(viewPoint) : viewPoint : RtPoint -> {x y z}
// cam(rollAngle) : rollAngle : float -> float
// cam(fStop) : fStop : float -> float
// cam(focalLength) : focalLength : float -> float
// cam(focalDistance) : focalDistance : float -> float
// cam(frameNumber) : frameNumber : int -> int
// cam(tesselation)
// cam(lowRezTesselation)
// cam(renderStyle) : 
// cam(movingRenderStyle) : 

// NOT DONE YET!
// cam(pixelAspectRatio) : pixelAspectRatio : float -> float
// cam(projectionType) : projectionType : N3DProjectionType -> string
// cam(backgroundColor) : backgroundColor : NXColor -> {r g b}
// cam(doesDrawBackgroundColor) : doesDrawBackgroundColor : BOOL -> int
// cam(nearPlane) : nearPlane : float -> float
// cam(farPlane) : farPlane : float -> float


// need to talk about interdependencies of these variables...
// send mail to Dan at Pixar and ask, especially about fStop, focalLength, and focalDistance...

- setupTraces
{
  char         *valAsString, *varName;
  RtPoint      anEyePoint, aViewPoint;
  RtFloat      aRollAngle;


  valAsString = (char *)NXZoneCalloc([self zone], 256, sizeof(char));
  varName = (char *)NXZoneCalloc([self zone], 256, sizeof(char));

  // need to pull out my current camera parameters and send them as
  // "set cam(paramName) paramValue" to my interp here...
  // and then we need to set up read and write traces so that any set calls
  // on any cam() variable will be in synch with this instance...
  

  // you may be asking yourself here: "why is wave, that bozo, copying
  // the strings that correspond to the variables names into a tmp
  // variable?  Is he really that wasteful, just because he has bigger
  // and more expensive equipment than I do?  Is he intentionally wasting
  // my precious time?"  Well, sports fans, no.  The problem is that
  // the current (7.3) implementation of tcl still uses a performance
  // hack that gets bit by compilers being smart.  The short answer is
  // if you hand tcl a const char* that the compiler has squirreled away
  // somewhere in read-only space, you will dump core, because tcl,
  // while doing a comparison on array elements, attempts to stick a '\0'
  // where the '(' is so it can do a fast hash table lookup.  It's very
  // good about taking the '\0' out once its done, but unfortunately, 
  // you'll never get the chance...  This is reasonably well documented
  // in the tcl doc, and given the efficiency, I can't really complain,
  // but it *is* a bug (whether in C, the compiler, or tcl, you make the call).
  // Anyway, to get around it, you either use the setVar2 version, or
  // you make sure that the string you hand into setVar is writable.
  // gak...

  // 
  strcpy(varName, "cam(renderStyle)");
  strcpy(varName, "SmoothSolids"); // do a default
  if ([self renderStyle] == N3D_PointCloud)
  {  strcpy(valAsString, "PointCloud");
  }
  if (renderStyle == N3D_WireFrame)
  {  strcpy(valAsString, "WireFrame");
  }
  if (renderStyle == N3D_ShadedWireFrame)
  {  strcpy(valAsString, "ShadedWireFrame");
  }
  if (renderStyle == N3D_FacetedSolids)
  {  strcpy(valAsString, "FacetedSolids");
  }
  if (renderStyle == N3D_SmoothSolids)
  {  strcpy(valAsString, "SmoothSolids");
  }
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForRenderStyle usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForRenderStyle usingData:(ClientData)self];

  // 
  strcpy(varName, "cam(movingRenderStyle)");
  strcpy(varName, "FacetedSolids"); // do a default
  if ([self movingRenderStyle] == N3D_PointCloud)
  {  strcpy(valAsString, "PointCloud");
  }
  if (movingRenderStyle == N3D_WireFrame)
  {  strcpy(valAsString, "WireFrame");
  }
  if (movingRenderStyle == N3D_ShadedWireFrame)
  {  strcpy(valAsString, "ShadedWireFrame");
  }
  if (movingRenderStyle == N3D_FacetedSolids)
  {  strcpy(valAsString, "FacetedSolids");
  }
  if (movingRenderStyle == N3D_SmoothSolids)
  {  strcpy(valAsString, "SmoothSolids");
  }
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForMovingRenderStyle usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForMovingRenderStyle usingData:(ClientData)self];

  // 
  strcpy(varName, "cam(tesselation)");
  sprintf(valAsString, "%f", [self tesselation]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForTesselation usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForTesselation usingData:(ClientData)self];

  // 
  strcpy(varName, "cam(lowRezTesselation)");
  sprintf(valAsString, "%f", [self lowRezTesselation]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForLowRezTesselation usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForLowRezTesselation usingData:(ClientData)self];

  // 
  strcpy(varName, "cam(fieldOfView)");
  sprintf(valAsString, "%f", [self fieldOfView]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForFieldOfViewByAngle usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForFieldOfViewByAngle usingData:(ClientData)self];

  //
  [self getEyeAt:&anEyePoint toward:&aViewPoint roll:&aRollAngle];

  strcpy(varName, "cam(eyePoint)");
  sprintf(valAsString, "%f %f %f", N3D_XComp(anEyePoint), N3D_YComp(anEyePoint), N3D_ZComp(anEyePoint));
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForEyePoint usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForEyePoint usingData:(ClientData)self];

  strcpy(varName, "cam(viewPoint)");
  sprintf(valAsString, "%f %f %f", N3D_XComp(aViewPoint), N3D_YComp(aViewPoint), N3D_ZComp(aViewPoint));
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForViewPoint usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForViewPoint usingData:(ClientData)self];

  strcpy(varName, "cam(rollAngle)");
  sprintf(valAsString, "%f", aRollAngle); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForRollAngle usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForRollAngle usingData:(ClientData)self];

  // 
  strcpy(varName, "cam(fStop)");
  sprintf(valAsString, "%f", [self fStop]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForFStop usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForFStop usingData:(ClientData)self];
  // 
  strcpy(varName, "cam(focalLength)");
  sprintf(valAsString, "%f", [self focalLength]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForFocalLength usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForFocalLength usingData:(ClientData)self];
  // 
  strcpy(varName, "cam(focalDistance)");
  sprintf(valAsString, "%f", [self focalDistance]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForFocalDistance usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForFocalDistance usingData:(ClientData)self];
  // 
  strcpy(varName, "cam(frameNumber)");
  sprintf(valAsString, "%d", [self frameNumber]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForFrameNumber usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForFrameNumber usingData:(ClientData)self];
  // 
  strcpy(varName, "cam(shotLength)");
  sprintf(valAsString, "%f", [self shotLength]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForShotLength usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForShotLength  usingData:(ClientData)self];
  // 
  strcpy(varName, "cam(shotStartTime)");
  sprintf(valAsString, "%f", [self shotStartTime]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForShotStartTime usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForShotStartTime  usingData:(ClientData)self];
  // 
  strcpy(varName, "cam(framesPerSecond)");
  sprintf(valAsString, "%f", [self framesPerSecond]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForFramesPerSecond usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForFramesPerSecond usingData:(ClientData)self];
  // 
  strcpy(varName, "cam(exposureLengthFactor)");
  sprintf(valAsString, "%f", [self exposureLengthFactor]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForExposureLengthFactor usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForExposureLengthFactor usingData:(ClientData)self];
  // 
  strcpy(varName, "cam(shutterOpenTime)");
  sprintf(valAsString, "%f", [self shutterOpenTime]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForShutterOpenTime usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForShutterOpenTime usingData:(ClientData)self];
  // 
  strcpy(varName, "cam(shutterCloseTime)");
  sprintf(valAsString, "%f", [self shutterCloseTime]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForShutterCloseTime usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForShutterCloseTime usingData:(ClientData)self];
  // 
  strcpy(varName, "cam(exposureLength)");
  sprintf(valAsString, "%f", [self exposureLength]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForExposureLength usingData:(ClientData)self];
  [tclInterp traceReadsOn:varName andCall:(Tcl_VarTraceProc *)ReadProcForExposureLength usingData:(ClientData)self];
  // 
  // 
  strcpy(varName, "cam(interpolationProc)");
  // need to set up a default interpolation proc...
  sprintf(valAsString, "noop");
  [tclInterp setVar:varName toValue:valAsString];
  [tclInterp traceWritesOn:varName andCall:(Tcl_VarTraceProc *)WriteProcForInterpolationProc usingData:(ClientData)self];

  // continue for all variables...


  inSynchWithTclInterp = YES;
  NXZoneFree([self zone], valAsString);
  NXZoneFree([self zone], varName);


  return self;
}


- initFrame:(const NXRect *)r 
{
   RtPoint fromP = {0,0,-5.0}, toP = {0,0,0};
   id aShape;


   [super initFrame:r];

   // speed stuff up and help workaround HP 3.2 bug (32 bit w/qrman crashes WS)
   [self setOpaque:YES];

   tclInterp = [[WWInterp alloc] init];

   [self initInterp];

   renderStyle = N3D_SmoothSolids;
   movingRenderStyle = N3D_FacetedSolids;

   scaleUpFactor = 1.05;
   scaleDownFactor = 0.95;
   translateXFactor = 0.01;
   translateYFactor = 0.01;
   translateZFactor = 0.05;
   epsilon = 4.0;
   selectionWidthEpsilon = 8.0;
   selectionHeightEpsilon = 8.0;
   shadingRate = 4.0;
   renderAtMediumRate = 1;
   renderAtHighRate = 2;
   renderCheckTimeSlice = 3.0;
   showSelectedShape = YES;
   drawOriginForSelectedShape = NO;
   useRendribInstead = NO;
   binaryRIB = NO;

   // setup 3D camera stuff
   [self setEyeAt:fromP toward:toP roll:0.0];

  [self setupDefaultLights];
  otherLightList = [[List alloc] init];

  // setup some initial global rendering state
  [self setSurfaceTypeForAll:renderStyle chooseHider:YES];	
  lowRezTesselationVector[0] = lowRezTesselationVector[1] = 16.0;
  tesselationVector[0] = tesselationVector[1] = 32.0;
  RiGeometricApproximation(RI_TESSELATION, RI_PARAMETRIC, tesselationVector, RI_NULL);

  // stick in a reasonable shader
  theShader=[[WW3DShader alloc] init];
  [theShader setUseColor:YES];
  [theShader setColor:ribColor];
  [(N3DShader *)theShader setShader:"plastic"];

  // stick in a reasonable reference shape
  aShape = [[WW3DShape alloc] init];
  [aShape setShapeName:NXCopyStringBuffer("defaultTorus")];
  [aShape setShapeName:NXCopyStringBuffer("WW3DWell")];
  [aShape appendRIBCommand:[[RIBColor alloc] init]];
  //[aShape appendRIBCommand:[[RIBTorus alloc] init]];

  // To show off the RIBCommand archiving bug, uncomment the next
  // command, recompile, load up the WW3DPalette in IB (running IB in gdb),
  // and drag the well off the palette...

  [aShape appendRIBCommand:[[WW3DText alloc] initWithCharPath:"WW3DWell" 
  			                     usingFont:[Font newFont:"Helvetica" 
  						             size:36] 
  			                     myShape:aShape justification:NX_CENTERED]];

  [aShape calculateBoundingBoxStartingAt:0.0 endingAt:0.0];

  [aShape  setShader_:theShader];
  [[self setWorldShape:aShape] free]; // free the default world shape
  currentShape = nil;
  [self setCurrentShape:[self worldShape]];  // set current shape to be top

  [self setSurfaceTypeForAll:N3D_SmoothSolids chooseHider:YES];

  theRotator = [[N3DRotator alloc] initWithCamera:self];
  [theRotator setRotationAxis:N3D_AllAxes];

  [self setPreTransformMatrix:(RtFloat (*)[4])N3DIdentityMatrix];
  statusBufSize = 256;
  statusBuf = (char *)NXZoneCalloc([self zone], statusBufSize, sizeof(char));

  fStop = RI_INFINITY;  // pinhole camera
  focalLength = 50;
  focalDistance = abs((N3D_ZComp(fromP) - N3D_ZComp(toP)));

  shutterOpenTime = 0.0;
  framesPerSecond = 15.0;
  frameTimeIncrement = 1.0/framesPerSecond;  // start off with 15 fps
  //savedExposureLength = exposureLength = frameTimeIncrement;  // start off with a motion blurred still camera
  savedExposureLength = exposureLength = 0.0;  // start off with a strobe still camera
  // start off with a film-like motion picture camera
  exposureLengthFactor = .5;
  
  // shot stuff
  shotStartTime = 0.0; // begin at the beginning...
  shotLength = 3.0; // let's start off with a 3 second shot
  frameNumber = 0;
  startFrame = 0;
  endFrame =  shotLength * framesPerSecond; 

  [self setShooting:NO];
  justFinishedShooting = NO;
  justResetExposureLength = NO;

  [self setupTraces];
  
    /*
     * Direct interaction support.
     */
    updateShape = NO;
    updateWhenTracking = NO;
    changeSelectedShape = NO;


  fontName = NXCopyStringBuffer("Helvetica");
  fontSize = 10.0;
  fontSpacing = 2.0;
  spacingFactor = -1.0/72.0;
  tabLength = -20 * spacingFactor;
  //timeFactor = 1.0/72.0;
  timeFactor = 3.0;
  sampleOffset = 10 * timeFactor;

  runTimeSystem = nil;

  return self;
}

// the receptor gets sent this message when the object is dragged into
// the File window, and when it is unarchived from the nib file.
- awake
{
  [super awake];

  // everything else has just been unarchived...

  [self initInterp];
  theRotator = [[N3DRotator alloc] initWithCamera:self];
  [theRotator setRotationAxis:N3D_AllAxes];

  RiGeometricApproximation(RI_TESSELATION, RI_PARAMETRIC, tesselationVector, RI_NULL);

  currentShape = nil;
  [self setCurrentShape:[self worldShape]];  // set current shape to be top

  statusBufSize = 256;
  statusBuf = (char *)NXZoneCalloc([self zone], statusBufSize, sizeof(char));

  frameTimeIncrement = 1.0/framesPerSecond;
  savedExposureLength = exposureLength;
  justFinishedShooting = NO;
  justResetExposureLength = NO;

  fontName = NXCopyStringBuffer("Helvetica");
  fontSize = 10.0;
  fontSpacing = 2.0;
  spacingFactor = -1.0/72;
  tabLength = -20 * spacingFactor;
  timeFactor = 1.0/72.0;
  sampleOffset = 10 * timeFactor;

  [self setupTraces];

  // for right now, disable binary rib output until I fix it...
  binaryRIB = NO;

  runTimeSystem = nil;

  return self;
}

- free

{
  id  theInterp;


  //NXLogError("WW3DCamera %p being free'ed\n", self);
  
  if (statusBuf) { /* NXLogError("freeing statusBuf %p\n", statusBuf);*/ NXZoneFree([self zone], statusBuf); }
  [self removeAnimateTE];
  [self removeRenderTE];

  if (fontName) { /* NXLogError("freeing fontName %p\n", fontName); */ free(fontName); }

  // MAKE SURE YOU'VE FREED EVERYTHING EXCEPT worldShape AND tclInterp BY THIS POINT!!!!!

  // make sure you free the tcl interp after the worldShape, because
  // EveCommands that get free'd will want to untrace their corresponding
  // tcl variables, and we don't want them sending messages to an already
  // free'd tclInterp
  // This only applies if there *is* a worldShape...

  if (worldShape)
  {  theInterp = tclInterp;
     //NXLogError("WW3DCamera %p being free'ed\n", self);
     [super free];
     NXLogError("about to free tclInterp %p (after super)\n", theInterp);
     [theInterp free];
  }
  else
  {  //NXLogError("about to free tclInterp %p (before super)\n", theInterp);
     [tclInterp free];
     [super free];
  }

  return nil;
}


- synchToSceneClock:aSceneClock
{
  float  sceneTime = [aSceneClock timestamp];


  // the idea here is to advance to the first frame that shows the scene's time.
  [self setFrameNumber:(int)(sceneTime * framesPerSecond)];
  [self display];
  return self;
}


- dumpCameraStateTo:(const char *)camName
{
  char         *valAsString, *varName;
  RtPoint      anEyePoint, aViewPoint;
  RtFloat      aRollAngle;


  valAsString = (char *)NXZoneCalloc([self zone], 256, sizeof(char));
  varName = (char *)NXZoneCalloc([self zone], 256, sizeof(char));

  // need to pull out my current camera parameters and send them as
  // "set camName(paramName) paramValue" to my interp here...
  // and then we need to set up write traces so that any set calls fail

  // 
  sprintf(varName, "%s(fieldOfView)", camName);
  sprintf(valAsString, "%f", [self fieldOfView]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  //
  [self getEyeAt:&anEyePoint toward:&aViewPoint roll:&aRollAngle];

  sprintf(varName, "%s(eyePoint)", camName);
  sprintf(valAsString, "%f %f %f", N3D_XComp(anEyePoint), N3D_YComp(anEyePoint), N3D_ZComp(anEyePoint));
  [tclInterp setVar:varName toReadOnlyValue:valAsString];

  sprintf(varName, "%s(viewPoint)", camName);
  sprintf(valAsString, "%f %f %f", N3D_XComp(aViewPoint), N3D_YComp(aViewPoint), N3D_ZComp(aViewPoint));
  [tclInterp setVar:varName toReadOnlyValue:valAsString];

  sprintf(varName, "%s(rollAngle)", camName);
  sprintf(valAsString, "%f", aRollAngle); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  // 
  sprintf(varName, "%s(fStop)", camName);
  sprintf(valAsString, "%f", [self fStop]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  // 
  sprintf(varName, "%s(focalLength)", camName);
  sprintf(valAsString, "%f", [self focalLength]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  // 
  sprintf(varName, "%s(focalDistance)", camName);
  sprintf(valAsString, "%f", [self focalDistance]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  // 
  sprintf(varName, "%s(frameNumber)", camName);
  sprintf(valAsString, "%d", [self frameNumber]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  // 
  sprintf(varName, "%s(shutterOpenTime)", camName);
  sprintf(valAsString, "%f", [self shutterOpenTime]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  // 
  sprintf(varName, "%s(shutterCloseTime)", camName);
  sprintf(valAsString, "%f", [self shutterCloseTime]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  // 
  sprintf(varName, "%s(frameTimeIncrement)", camName);
  sprintf(valAsString, "%f", [self frameTimeIncrement]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  // 
  sprintf(varName, "%s(exposureLength)", camName);
  sprintf(valAsString, "%f", [self exposureLength]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  // 

  NXZoneFree([self zone], valAsString);
  NXZoneFree([self zone], varName);

  return self;
}


- restoreCameraStateFrom:(const char *)camName
{
  char         *valAsString = NULL, 
               *varName;
  RtPoint      anEyePoint, aViewPoint;
  RtFloat      aRollAngle;


  varName = (char *)NXZoneCalloc([self zone], 256, sizeof(char));

  // need to pull out my current camera parameters and send them as
  // "set camName(paramName) paramValue" to my interp here...
  // and then we need to set up write traces so that any set calls fail

  // 
  sprintf(varName, "%s(fieldOfView)", camName);
  valAsString = [tclInterp getVar:varName];
  [self setFieldOfViewByAngle:(float)atof(valAsString)];
  // should I free valAsString here?

  //
  [self getEyeAt:&anEyePoint toward:&aViewPoint roll:&aRollAngle];

  sprintf(varName, "%s(eyePoint)", camName);
  sprintf(valAsString, "%f %f %f", N3D_XComp(anEyePoint), N3D_YComp(anEyePoint), N3D_ZComp(anEyePoint));
  [tclInterp setVar:varName toReadOnlyValue:valAsString];

  sprintf(varName, "%s(viewPoint)", camName);
  sprintf(valAsString, "%f %f %f", N3D_XComp(aViewPoint), N3D_YComp(aViewPoint), N3D_ZComp(aViewPoint));
  [tclInterp setVar:varName toReadOnlyValue:valAsString];

  sprintf(varName, "%s(rollAngle)", camName);
  sprintf(valAsString, "%f", aRollAngle); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  // 
  sprintf(varName, "%s(fStop)", camName);
  sprintf(valAsString, "%f", [self fStop]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  // 
  sprintf(varName, "%s(focalLength)", camName);
  sprintf(valAsString, "%f", [self focalLength]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  // 
  sprintf(varName, "%s(focalDistance)", camName);
  sprintf(valAsString, "%f", [self focalDistance]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  // 
  sprintf(varName, "%s(frameNumber)", camName);
  sprintf(valAsString, "%d", [self frameNumber]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  // 
  sprintf(varName, "%s(shutterOpenTime)", camName);
  sprintf(valAsString, "%f", [self shutterOpenTime]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  // 
  sprintf(varName, "%s(shutterCloseTime)", camName);
  sprintf(valAsString, "%f", [self shutterCloseTime]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  // 
  sprintf(varName, "%s(frameTimeIncrement)", camName);
  sprintf(valAsString, "%f", [self frameTimeIncrement]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  // 
  sprintf(varName, "%s(exposureLength)", camName);
  sprintf(valAsString, "%f", [self exposureLength]); // turn the value into its tcl equivalent
  [tclInterp setVar:varName toReadOnlyValue:valAsString];
  // 

  NXZoneFree([self zone], valAsString);
  NXZoneFree([self zone], varName);

  return self;
}


- evaluateCameraInterpolationRoutineWith:(float)u
{
   char   cmd[80];


   sprintf(cmd, "eval $cam(interpolationProc) %f", u);
   //NXLogError("WW3DCamera's interp evaluating camera interpolation routine with %f for frame number %d here...\n", 
   //	      u, [self frameNumber]);
   [tclInterp globalEval:cmd];
   return self;
}

- renderMapsToStream:(NXStream *)ns
{
  [worldShape renderMaps:self usingStream:ns];
  return self;
}

- renderMaps
{
  [worldShape renderMaps:self];
  return self;
}

- worldBegin:(RtToken)context 
{ 
   RtInt        jitterFlag = 1; 
   float        u;
   int          theFrameNumber;
   static char  *offPtr = "off";
   static int   patchMaxLevel = 4.0;


   if (dumpingToRIB)
   {  //static RtInt v31 = 301; 
      //RiOption(RI_ARCHIVE, "outputversion", &v31, RI_NULL);

      [self renderMaps];
   }
   if (useRendribInstead)
   {  RiArchiveRecord(RI_COMMENT, "the following declare all the non-standard variables for rendrib");
      RiArchiveRecord(RI_COMMENT, "rendrib declares these internally, but prman or WavesWorld would complain if");
      RiArchiveRecord(RI_COMMENT, "it got passed a RIB file with these attributes if they hadn't been declared");
      RiDeclare("include", "uniform string");
      RiDeclare("shadows", "uniform string");
      RiDeclare("verbosity", "uniform string");
      RiDeclare("casts_shadows", "uniform string");
      RiDeclare("zonal", "uniform string");
      RiDeclare("patchsize", "uniform float");
      RiDeclare("elemsize", "uniform float");
      RiDeclare("minsize", "uniform float");
      RiDeclare("displacement", "uniform float");
      RiDeclare("patch_maxlevel", "uniform integer");
      RiDeclare("max_raylevel", "uniform integer");
      RiDeclare("minshadowbias", "uniform float");
      RiDeclare("minsamples", "uniform integer");
      RiDeclare("maxsamples", "uniform integer");
      RiDeclare("branch_ratio", "uniform integer");
      RiDeclare("max_branch_ratio", "uniform integer");
      RiDeclare("binary", "uniform integer");
      RiDeclare("averagecolor", "uniform color");
      RiDeclare("emissioncolor", "uniform color");      
      RiArchiveRecord(RI_COMMENT, "the following turns off shadows as the default for this file when using rendrib");
      RiAttribute("light", "shadows", &offPtr, RI_NULL);
   }
   else
   {  RiArchiveRecord(RI_COMMENT, "a shader included in prman 3.4 used to ensure color doesn't exceed alpha");
      RiImager("clamptoalpha", RI_NULL);
   } 

   // okay, if the shading rate is less than 1, use PixelSamples 3 3 
   // okay, if the shading rate is greater than or equal to 1 but less than 4, use PixelSamples 2 2 
   // okay, if the shading rate is greater than or equal to 4, use PixelSamples 1 1 
   if ((shadingRate < 1.0) && (exposureLength > 0.0))
   {  if (useRendribInstead)
      {  RiArchiveRecord(RI_COMMENT, "the following stops patch subdivision at level 8 when using rendrib.  If your geometry looks blocky, make the value bigger");
         patchMaxLevel = 8;
         RiAttribute("render", "patch_maxlevel", &patchMaxLevel, RI_NULL);
         RiArchiveRecord(RI_COMMENT, "please note: I'm purposely setting the PixelSamples to 3 3 because you're using such a high quality shading rate");
         RiArchiveRecord(RI_COMMENT, "and there is potentially motion blur in this frame.");
         RiArchiveRecord(RI_COMMENT, 
			 "Since you're probably using rendrib, though, the ShadingRate is ignored, but things will render faster if the PixelSamples was lower.");
      }
      else
      {  RiArchiveRecord(RI_COMMENT, 
			 "please note: I'm purposely setting the PixelSamples to 3 3 because you're using such a high quality shading rate.");
	 RiArchiveRecord(RI_COMMENT, 
			 "Assuming you're using prman, things will render faster if this number was lower, or the ShadingRate value was higher.");
      }
      RiPixelSamples(3.0, 3.0);
   }
   else
   {  if (shadingRate < 4.0)
      {  if (useRendribInstead)
         {  RiArchiveRecord(RI_COMMENT, "the following stops patch subdivision at level 4 when using rendrib.  If your geometry looks blocky, make the value bigger");
            patchMaxLevel = 4;
            RiAttribute("render", "patch_maxlevel", &patchMaxLevel, RI_NULL);
            RiArchiveRecord(RI_COMMENT, 
			    "please note: I'm purposely setting the PixelSamples to 2 2 because you're using a reasonable quality shading rate.");
	    RiArchiveRecord(RI_COMMENT, 
			    "Since you're probably using rendrib, though, the ShadingRate is ignored, but things will render faster if the PixelSamples was lower.");
	 }
         else
         {  RiArchiveRecord(RI_COMMENT, 
			    "please note: I'm purposely setting the PixelSamples to 2 2 because you're using  a reasonable quality shading rate.");
	    RiArchiveRecord(RI_COMMENT, 
			    "Assuming you're using prman, things will render faster if this number was lower, or the ShadingRate value was higher.");
 	 }
	 RiPixelSamples(2.0, 2.0);
      }
      else
      {  if (useRendribInstead)
         {  RiArchiveRecord(RI_COMMENT, "the following stops patch subdivision at level 2 when using rendrib.  If your geometry looks blocky, make the value bigger");
            patchMaxLevel = 2;
            RiAttribute("render", "patch_maxlevel", &patchMaxLevel, RI_NULL);
            RiArchiveRecord(RI_COMMENT, 
			    "please note: I'm purposely setting the PixelSamples to 1 1 because you're using a low quality shading rate.");
	    RiArchiveRecord(RI_COMMENT, 
			    "Since you're probably using rendrib, though, the ShadingRate is ignored, but things will look better if the PixelSamples was higher.");
	 }
         else
         {  RiArchiveRecord(RI_COMMENT, 
			    "please note: I'm purposely setting the PixelSamples to 1 1 because you're using  a low quality shading rate.");
	    RiArchiveRecord(RI_COMMENT, 
			    "Assuming you're using prman, things will look better if this number was higher, or the ShadingRate value was lower.");
 	 }
	 RiPixelSamples(1.0, 1.0);
      }
   }


   // Run camera interpolation procedure here. We do it here (as opposed
   // to doing it in drawSelf:), because at this point, all the camera
   // parameters are as fixed as they're gonna get for this frame.

   // when a new frame number is set with setFrameNumber:, it sets the 
   // BOOL evaluateInterpProc.  We only want to run this proc once per
   // frame, but we also don't want to do it until this point.  So...
   // at this point, if the flag is set, we evaluate the proc with
   // the current value of u.
   // note that if the currentFrame == startFrame, and the flag is set,
   // we need to do some initial work to set up the state.  We need to
   // fill out the global tcl array startCam(), which is to have the state 
   // of the current camera in read-only variables...

   if (shooting)
   {  theFrameNumber = [self frameNumber];

      if (theFrameNumber == 0) // sanity check!
      {  savedExposureLength = exposureLength; 
         [self setExposureLength:([self frameTimeIncrement] * exposureLengthFactor)];
      }
      if (evaluateInterpProc)
      {  if (theFrameNumber == 0) 
         {  // initialize startCam() array with my current state 
            [tclInterp unsetVar:"startCam"];
            [self dumpCameraStateTo:"startCam"];  
            u = 0;
         } 
         else
         {  // calculate u, where u is 0 at startFrame and 1 at endFrame
            // let's say startFrame is 0, endFrame is 100, and frameNumber is 25
            // we want u to be .25
            // note that we should not have to worry about div by zero here...
            u = (float)theFrameNumber/(float)([self endFrame] - 1);
         }
         [self evaluateCameraInterpolationRoutineWith:u];
         evaluateInterpProc = NO;
      }
   }
   else  // if we're not shooting, we want the camera to be in synch with the scene clock and be a strobe camera...
   {  // ask the sceneClock what time it is, and make ourselves a strobe camera
      if (justFinishedShooting) // when we come out of a shot, reset the exposureLength to what it was previously
      {  [self setExposureLength:savedExposureLength]; // need to do it "correctly", so tcl is notified
         justFinishedShooting = NO;
      }
      [self setShutterOpenTime:[sceneClock timestamp]];  // need to do it "correctly", so tcl is notified
   }

   // If I want to support depth-of-field, need to call RiDepthOfField()
   RiDepthOfField(fStop, focalLength, focalDistance);
   // If I want to support motion blur, need to call RiShutter(), as well 
   // as having  RiMotionBegin/End blocks around primitives
   if (shooting)
   {  RiShutter(shutterOpenTime, (shutterOpenTime + (exposureLength * exposureLengthFactor)));
   }
   else
   {  RiShutter(shutterOpenTime, (shutterOpenTime + exposureLength));
   }
   if (exposureLength > 0.0)  // is it a strobe or not?
   {  // optimize for motion blur...
      // really should find out what the hider has been set to...
      RiHider("hidden", "jitter", &jitterFlag, RI_NULL);
   }

   // or whatever the heck is the right thang...
   if (renderCount == renderAtMediumRate)
   {  RiShadingRate(50.0);
   } 
   else
   {  if (renderCount == renderAtHighRate)
      {  RiShadingRate(5.0);
      }
   }
 
   [super worldBegin:context];
   return self;
}

- getTransformMatrix:(RtMatrix)aMatrix
{
  N3D_CopyMatrix(transform, aMatrix);
  return self;
}

- takeStatusText:sender  { statusText = sender; return self; }

- delegate { return delegate; }

/////////////////////// 
// 3D stuff
/////////////////////// 
- setNoCurrentShape:sender { currentShape = nil; return self; }

- currentShape { return currentShape; }
- setCurrentShape:newCurrentShape
{
  // should make sure newShape is a child of worldShape...
  [currentShape setSelected:NO andDrawOrigin:NO];

  [worldShape setDrawAsBox:drawWorldAsBox];

  if (drawCurrentAsBox)
  {  [currentShape setDrawAsBox:NO];
  }

  currentShape = newCurrentShape;
  [currentShape setSelected:showSelectedShape andDrawOrigin:drawOriginForSelectedShape];
  if (drawCurrentAsBox)
  {  [currentShape setDrawAsBox:YES];
  }
  
  // WAVE this is expensive; I should have a flag...
  //[self display];

  if (statusText) // potentially record activity
  {  sprintf(statusBuf, "currentShape is: %s", [currentShape shapeName]);
     [statusText setStringValue:statusBuf];
  }

  return self;
}

- (RtFloat)tesselation { return tesselationVector[0]; }

- setTesselation:(RtFloat)newValue 
{ 
  tesselationVector[0] = tesselationVector[1] = newValue;
  RiGeometricApproximation(RI_TESSELATION, RI_PARAMETRIC, tesselationVector, RI_NULL);

  return self;
}

- takeTesselation:sender
{
  tesselationVector[0] = tesselationVector[1] = [sender floatValue];
  RiGeometricApproximation(RI_TESSELATION, RI_PARAMETRIC, tesselationVector, RI_NULL);

  if (statusText) // potentially record activity
  {  sprintf(statusBuf, "tesselation set to (%f %f)", 
	     tesselationVector[0], tesselationVector[1]);
     [statusText setStringValue:statusBuf];
  }

  [self display];

  return self;
}

- (RtFloat)lowRezTesselation { return lowRezTesselationVector[0]; }

- setLowRezTesselation:(RtFloat)newValue 
{ 
  lowRezTesselationVector[0] = lowRezTesselationVector[1] = newValue;

  return self;
}

- takeLowRezTesselation:sender
{
  lowRezTesselationVector[0] = lowRezTesselationVector[1] = [sender floatValue];
  if (statusText) // potentially record activity
  {  sprintf(statusBuf, "low rez tesselation set to (%f %f)", 
	     lowRezTesselationVector[0], lowRezTesselationVector[1]);
     [statusText setStringValue:statusBuf];
  }
  return self;
}

- (float)shadingRate { return shadingRate; }
- takeShadingRate:sender
{
  shadingRate = [sender floatValue];
  if (shadingRate < 0.25)
  {  shadingRate = 0.25;
  }
  if (shadingRate > 100.0)
  {  shadingRate = 100.0;
  }
  return self;
}

- takeAmbientLightState:sender
{
   [ambientLight switchLight:[sender intValue]];
   return [self display];
}
- takeAmbientLightIntensity:sender
{
   [ambientLight setIntensity:[sender floatValue]];
   return [self display];
}
- takeAmbientLightColor:sender
{
   [ambientLight setColor:[sender color]];
   return [self display];
}
- takeLeftLightState:sender
{
   [leftLight switchLight:[sender intValue]];
   return [self display];
}
- takeLeftLightIntensity:sender
{
   [leftLight setIntensity:[sender floatValue]];
   return [self display];
}
- takeLeftLightColor:sender
{
   [leftLight setColor:[sender color]];
   return [self display];
}
- takeRightLightState:sender
{
   [rightLight switchLight:[sender intValue]];
   return [self display];
}
- takeRightLightIntensity:sender
{
   [rightLight setIntensity:[sender floatValue]];
   return [self display];
}
- takeRightLightColor:sender
{
   [rightLight setColor:[sender color]];
   return [self display];
}

- (BOOL)ambientLightState { return [ambientLight on]; }
- (RtFloat)ambientLightIntensity { return [ambientLight intensity]; }
- (NXColor)ambientLightColor { return [ambientLight color]; }

- (BOOL)leftLightState { return [leftLight on]; }
- (RtFloat)leftLightIntensity { return [leftLight intensity]; }
- (NXColor)leftLightColor { return [leftLight color]; }

- (BOOL)rightLightState { return [rightLight on]; }
- (RtFloat)rightLightIntensity { return [ rightLight intensity]; }
- (NXColor)rightLightColor { return [rightLight color]; }

- saveImage:sender
{
  static id savePanel=nil;
  NXStream *ts;


  if (!savePanel) 
  {  savePanel=[SavePanel new];
  }

  [savePanel setRequiredFileType:"tiff"];
  if([savePanel runModal])
  {  ts = NXOpenMemory(NULL, 0, NX_READWRITE);
     [image writeTIFF:ts allRepresentations:NO usingCompression:NX_TIFF_COMPRESSION_LZW andFactor:1.0];
     NXSaveToFile(ts, [savePanel filename]);
     NXCloseMemory(ts,NX_FREEBUFFER);
  }

  return self;
}


- dumpRIB:sender
{
  static id savePanel=nil;
  NXStream *ts;
  char filename[MAXPATHLEN+1];
  char *ascii = "ascii",
       *binary = "binary";
 

  dumpingToRIB = YES;

  if (statusText) // potentially record activity
  {  sprintf(statusBuf, "dumping RIB...");
     [statusText setStringValue:statusBuf];
  }

  if (!savePanel) 
  {  savePanel=[SavePanel new];
  }
  [savePanel setRequiredFileType:"rib"];

  if([savePanel runModal])
  {  // returned w/pathname, open a stream and
     ts = NXOpenMemory(NULL, 0, NX_WRITEONLY);

     // remove the .rib extension from the path returned by the SavePanel
     basename([savePanel filename], ".rib", filename);

     // feed to NXPrintf to put in the custom Display command
     NXPrintf(ts, "# This RIB file generated by Michael B. Johnson's WW3DKit software running atop NEXTSTEP.\n");
     NXPrintf(ts, "# Contact him at wave@media.mit.edu or (617) 666-4119 for more information.\n#\n");
     NXPrintf(ts, "Display \"%s.tiff\" \"file\" \"rgba\"\n", filename);
     NXPrintf(ts, "ShadingRate %f\n", shadingRate);

     // set the output format:
     if (binaryRIB)
     {  format = binary;
     }
     else
     {  format = ascii;
     }
     RiDeclare("format", "uniform string");
     RiOption("rib", "format", &format, RI_NULL);

     // then feed the rib code to the stream and
     [self copyRIBCode:ts];
     // save the stream to the file selected in the savepanel
     NXSaveToFile(ts, [savePanel filename]);

     // and close the stream (which also flushes it), also making sure
     // that the allocated memory is freed.
    NXCloseMemory(ts,NX_FREEBUFFER);
  }

  if (statusText) // potentially record activity
  {  sprintf(statusBuf, "done!");
     [statusText setStringValue:statusBuf];
  }
  dumpingToRIB = NO;
  return self;
}

- setRunTimeSystem:newRTS
{
   runTimeSystem = newRTS;
   return self;
}


- copyRIBCode:(NXStream *)ts
{
  id retID, tmpText = statusText;


  sprintf(statusBuf, "disabling status display to avoid NeXT's copyRIBCode: bug...");
  [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
  statusText = nil;
  [runTimeSystem disableStatusText];
  [self renderMapsToStream:ts];

  retID = [super copyRIBCode:ts];
  statusText = tmpText;
  [runTimeSystem reenableStatusText];
  return retID;
}


- dumpShotToRIBFile:(const char *)longFilename
{
  NXStream   *ts;
  char       filename[MAXPATHLEN+1];
  int        oldFrameNumber, i, startI, endI, incI;
  static id  savePanel=nil;
 

  if (statusText) // potentially record activity
  {  sprintf(statusBuf, "dumping complete shot to RIB file %s...", longFilename);
     [statusText setStringValue:statusBuf];
  }

  // make sure it's a RIB file (at least 1.rib)...
  if (longFilename && (strlen(longFilename) > 4) && (!strcmp(".rib", (const char *)(longFilename + strlen(longFilename) - 4))))
  {  // returned w/pathname, open a stream and
     ts = NXOpenMemory(NULL, 0, NX_WRITEONLY);

     // remove the .rib extension from the path returned by the SavePanel
     basename(longFilename, ".rib", filename);

     // feed to NXPrintf to put in the custom Display command
     NXPrintf(ts, "# This RIB file generated by Michael B. Johnson's WW3DKit software running atop NEXTSTEP.\n");
     NXPrintf(ts, "# Contact him at wave@media.mit.edu or (617) 666-4119 for more information.\n#\n");


     // we need to loop over the whole shot, starting at the first frame, and incrementing correctly.
     // The 3DKit will take care of putting in frame numbers, but we need to manage the names of the 
     // tiff files that will get saved out. The WWAnimatable and WWRenderable objects that this camera
     // is attached to will take care of figuring out what time it is when the camera takes their picture.

     NXPrintf(ts, "ShadingRate %f\n\n\n", shadingRate);
     
     oldFrameNumber = [self frameNumber];
     startI = [self startFrame];
     endI = [self endFrame];
     incI = [self frameIncrement];

     // due to a bug in NeXT's 3DKit, we want to make sure that
     // nothing gets written out to a view while we're in
     // "copyRIBCode:". Unfortunately, that's pretty hard...  The most likely
     // culprit is a routine (like the current camera's interpolation routine)
     // writing out to the "statusText" outlet.  Probably the best thing to do
     // then is to save the value of that outlet and set it to nil for the
     // duration of copyRIBCode:...  actually, the better thing to do is
     // probably reimplement copyRIBCode:, where I unset the outlet, call
     // the super's version, and then set it back.  Yea, let's try that...

     shooting = YES;
     [self setFrameNumber:(startI - 1)]; // just to flush state...
     for (i = startI; i < endI; i += incI)
     {  [self setFrameNumber:i]; 
        NXPrintf(ts, "\n\n");
        NXPrintf(ts, "\nDisplay \"%s.%d.tiff\" \"file\" \"rgba\"\n", filename, i);
        [self copyRIBCode:ts];
     }
     NXSaveToFile(ts, [savePanel filename]);
     NXCloseMemory(ts,NX_FREEBUFFER);

     [self setFrameNumber:oldFrameNumber];
     shooting = NO;
  }
  else
  {  sprintf(statusBuf, "<%s> is not a valid filename for a RIB file.\n", longFilename);
     [statusText setStringValue:statusBuf];
     return nil;
  }
  if (statusText) // potentially record activity
  {  sprintf(statusBuf, "done!");
     [statusText setStringValue:statusBuf];
  }
  return self;
}


- dumpShotToRIB:sender
{
  static id  savePanel=nil;
  NXStream   *ts;
  char       filename[MAXPATHLEN+1];
  int        oldFrameNumber, i, startI, endI, incI;
  char *ascii = "ascii",
       *binary = "binary"; 
 

  if (statusText) // potentially record activity
  {  sprintf(statusBuf, "dumping complete shot to RIB...");
     [statusText setStringValue:statusBuf];
  }

  if (!savePanel) 
  {  savePanel=[SavePanel new];
  }
  [savePanel setRequiredFileType:"rib"];

  if([savePanel runModal])
  {  // returned w/pathname, open a stream and
     ts = NXOpenMemory(NULL, 0, NX_WRITEONLY);

     // remove the .rib extension from the path returned by the SavePanel
     basename([savePanel filename], ".rib", filename);

     // set the output format:
     if (binaryRIB)
     {  format = binary;
     }
     else
     {  format = ascii;
     }
     RiDeclare("format", "uniform string");
     RiOption("rib", "format", &format, RI_NULL);

     // feed to NXPrintf to put in the custom Display command
     NXPrintf(ts, "# This RIB file generated by Michael B. Johnson's WW3DKit software running atop NEXTSTEP.\n");
     NXPrintf(ts, "# Contact him at wave@media.mit.edu or (617) 666-4119 for more information.\n#\n");


     // we need to loop over the whole shot, starting at the first frame, and incrementing correctly.
     // The 3DKit will take care of putting in frame numbers, but we need to manage the names of the 
     // tiff files that will get saved out. The WWAnimatable and WWRenderable objects that this camera
     // is attached to will take care of figuring out what time it is when the camera takes their picture.

     NXPrintf(ts, "ShadingRate %f\n", shadingRate);
     
     oldFrameNumber = [self frameNumber];
     startI = [self startFrame];
     endI = [self endFrame];
     incI = [self frameIncrement];

     // due to a bug in NeXT's 3DKit, we want to make sure that
     // nothing gets written out to a view while we're in
     // "copyRIBCode:". Unfortunately, that's pretty hard...  The most likely
     // culprit is a routine (like the current camera's interpolation routine)
     // writing out to the "statusText" outlet.  Probably the best thing to do
     // then is to save the value of that outlet and set it to nil for the
     // duration of copyRIBCode:...  actually, the better thing to do is
     // probably reimplement copyRIBCode:, where I unset the outlet, call
     // the super's version, and then set it back.  Yea, let's try that...

     shooting = YES;
     [self setFrameNumber:(startI - 1)]; // just to flush state...
      // set scene time as we're moving through...
      [sceneClock setCurrentTimeQuietly:[self shutterOpenTime]];   // god this is gross!!
      [sceneClock setCurrentTimeQuietly:[self shutterCloseTime]];  // god this is gross!!

     for (i = startI; i < endI; i += incI)
     {  [self setFrameNumber:i]; 
        // set scene time as we're moving through...
        [sceneClock setCurrentTimeQuietly:[self shutterOpenTime]];   // god this is gross!!
        [sceneClock setCurrentTimeQuietly:[self shutterCloseTime]];  // god this is gross!!
        NXPrintf(ts, "Display \"%s.%d.tiff\" \"file\" \"rgba\"\n", filename, i);
        [self copyRIBCode:ts];
     }
     NXSaveToFile(ts, [savePanel filename]);
     NXCloseMemory(ts,NX_FREEBUFFER);

     [self setFrameNumber:oldFrameNumber];
     [sceneClock setCurrentTime:[self shutterOpenTime]];   // god this is gross!!
     shooting = NO;
  }

  if (statusText) // potentially record activity
  {  sprintf(statusBuf, "done!");
     [statusText setStringValue:statusBuf];
  }
  return self;
}


- dumpEve:sender
{
  static id savePanel=nil;
  NXStream *ts;


  if (statusText) // potentially record activity
  {  sprintf(statusBuf, "dumping Eve...");
     [statusText setStringValue:statusBuf];
  }

  if (!savePanel) 
  {  savePanel=[SavePanel new];
  }
  [savePanel setRequiredFileType:"eve"];

  if([savePanel runModal])
  {  // returned w/pathname, open a stream and
     ts = NXOpenMemory(NULL, 0, NX_WRITEONLY);

     // feed to NXPrintf to put in the custom Display command
     NXPrintf(ts, "# This eve model file generated by Michael B. Johnson's WW3DKit software running atop NEXTSTEP.\n");
     NXPrintf(ts, "# Contact him at wave@media.mit.edu or (617) 666-4119 for more information.\n#\n");

     [[worldShape tclInterp] writeState:ts];

     // then tell the worldShape to write itself out, which will recurse down...
     [worldShape writeEve:ts atTabLevel:0];

     // save the stream to the file selected in the savepanel
     NXSaveToFile(ts, [savePanel filename]);

     // and close the stream (which also flushes it), also making sure
     // that the allocated memory is freed.
    NXCloseMemory(ts,NX_FREEBUFFER);
  }

  if (statusText) // potentially record activity
  {  sprintf(statusBuf, "done!");
     [statusText setStringValue:statusBuf];
  }
  return self;
}


- dumpScene:sender
{
  static id savePanel=nil;
  NXStream *ts;


  if (statusText) // potentially record activity
  {  sprintf(statusBuf, "dumping scene...");
     [statusText setStringValue:statusBuf];
  }

  if (!savePanel) 
  {  savePanel=[SavePanel new];
  }
  [savePanel setRequiredFileType:"wwScene"];

  if([savePanel runModal])
  {  // returned w/pathname, open a stream and
     ts = NXOpenMemory(NULL, 0, NX_WRITEONLY);

     NXPrintf(ts, "#\n");
     NXPrintf(ts, "# This eve scene file generated by Michael B. Johnson's WW3DKit software running atop NEXTSTEP.\n");
     NXPrintf(ts, "# Contact him at wave@media.mit.edu or (617) 666-4119 for more information.\n#\n");

     [[worldShape tclInterp] writeState:ts];

     // then tell the worldShape to write itself out, which will recurse down...
     [worldShape writeScene:ts atTabLevel:0];

     // save the stream to the file selected in the savepanel
     NXSaveToFile(ts, [savePanel filename]);

     // and close the stream (which also flushes it), also making sure
     // that the allocated memory is freed.
    NXCloseMemory(ts,NX_FREEBUFFER);
  }

  if (statusText) // potentially record activity
  {  sprintf(statusBuf, "done!");
     [statusText setStringValue:statusBuf];
  }
  return self;
}

- dumpInventor:sender
{
  static id savePanel=nil;
  NXStream *ts;


  if (statusText) // potentially record activity
  {  sprintf(statusBuf, "dumping SGI Open Inventor 2.0 file...");
     [statusText setStringValue:statusBuf];
  }

  if (!savePanel) 
  {  savePanel=[SavePanel new];
  }
  [savePanel setRequiredFileType:"iv"];

  if([savePanel runModal])
  {  // returned w/pathname, open a stream and
     ts = NXOpenMemory(NULL, 0, NX_WRITEONLY);

     NXPrintf(ts, "#Inventor V2.0 ascii\n#\n");
     NXPrintf(ts, "# This Inventor file was automatically generated from an eve model file\n");
     NXPrintf(ts, "# generated by Michael B. Johnson's WW3DKit software running atop NEXTSTEP.\n");
     NXPrintf(ts, "# Contact him at wave@media.mit.edu or (617) 666-4119 for more information.\n#\n");
     NXPrintf(ts, "# At the tone, the  current time is: %f ... Beep!\n#\n#\n", shutterOpenTime);

     NXPrintf(ts, "Separator {\n");
     NXPrintf(ts, "\t# RenderMan is a left-handed coordinate system, Inventor is right-handed, so this should fix things, right?\n");
     NXPrintf(ts, "\tScale {\n");
     NXPrintf(ts, "\t\tscaleFactor 1 1 -1\n");
     NXPrintf(ts, "\t}\n");

     // then tell the worldShape to write itself out, which will recurse down...
     [worldShape writeInventorAtTime:shutterOpenTime to:ts atTabLevel:1];

     NXPrintf(ts, "}\n");

     // save the stream to the file selected in the savepanel
     NXSaveToFile(ts, [savePanel filename]);

     // and close the stream (which also flushes it), also making sure
     // that the allocated memory is freed.
    NXCloseMemory(ts,NX_FREEBUFFER);
  }

  if (statusText) // potentially record activity
  {  sprintf(statusBuf, "done!");
     [statusText setStringValue:statusBuf];
  }
  return self;
}


- (const char *)fontName { return fontName; }
- (float)fontSize { return fontSize; }
- (float)fontSpacing { return fontSpacing; }
- (float)spacingFactor { return spacingFactor; }
- (float)tabLength { return tabLength; }
- (float)timeFactor { return timeFactor; }
- (float)sampleOffset { return sampleOffset; }

- dump3DTextScene:sender
{
  static id savePanel=nil;
  NXStream *ts;


  if (statusText) // potentially record activity
  {  sprintf(statusBuf, "dumping 3D Text scene...");
     [statusText setStringValue:statusBuf];
  }

  if (!savePanel) 
  {  savePanel=[SavePanel new];
  }
  [savePanel setRequiredFileType:"eve"];

  if([savePanel runModal])
  {  // returned w/pathname, open a stream and
     ts = NXOpenMemory(NULL, 0, NX_WRITEONLY);

     NXPrintf(ts, "#\n");
     NXPrintf(ts, "# This eve 3D text scene file generated by Michael B. Johnson's WW3DKit software running atop NEXTSTEP.\n");
     NXPrintf(ts, "# Contact him at wave@media.mit.edu or (617) 666-4119 for more information.\n#\n");

     [[worldShape tclInterp] writeState:ts];

     NXPrintf(ts, "set __text__(fontName) %s\n",  [self fontName]);
     NXPrintf(ts, "set __text__(fontSize) %f\n",  [self fontSize]);
     NXPrintf(ts, "set __text__(spacing) %f\n",   [self fontSpacing]);
     NXPrintf(ts, "set __text__(spacingFactor) %f\n",   [self spacingFactor]);
     NXPrintf(ts, "set __text__(tabLength) %f\n", [self tabLength]);
     NXPrintf(ts, "set __text__(timeFactor) %f\n", [self timeFactor]);
     NXPrintf(ts, "set __text__(sampleOffset) %f\n", [self sampleOffset]);

     // then tell the worldShape to write itself out, which will recurse down...

     NXPrintf(ts, "startShape TextWorld\n");
       [worldShape write3DTextScene:ts atTabLevel:1 index:0 time:0 until:((float)-1)];
     NXPrintf(ts, "endShape\n");

     // save the stream to the file selected in the savepanel
     NXSaveToFile(ts, [savePanel filename]);

     // and close the stream (which also flushes it), also making sure
     // that the allocated memory is freed.
    NXCloseMemory(ts,NX_FREEBUFFER);
  }

  if (statusText) // potentially record activity
  {  sprintf(statusBuf, "done!");
     [statusText setStringValue:statusBuf];
  }
  return self;
}


- turnOffCropWindow
{ 
   selectionRegion.size.width = 0.0;
   selectionRegion.size.height = 0.0;
   return self;
}


//RtToken fileContext=NULL, solidContext=NULL;
//char *archive="archive",*bintype="binaryfile";

//solidContext=[[N3DContextManager new] mainContext]; // GET THE  
//CURRENT CONTEXT (USED BY THE 3DKit)
//fileContext=RiBegin("FileContext","renderer",&archive,"filepath",&ribFile,"format",&asciitype,RI_NULL);
//// PUT HERE RenderMan code...
//RiContext(fileContext,RI_NULL); RiEnd();
//RiContext(solidContext,RI_NULL);
//[[N3DContextManager new] setCurrentContext:solidContext];

- dumpBinaryRIBToFile:(char *)filename usingShadingRate:(float)sRate hider:(char *)hiderName pixelSamples:(float)pixelSampleX :(float)pixelSampleY
{
  NXRect    myFrame;
  char      buf[MAXPATHLEN+1];
  float     cropMinX, cropMinY, cropMaxX, cropMaxY;
  RtToken  fileContext = NULL, 
           wwContext = NULL;
  char     *archive = "archive",
           *bintype = "binaryfile";


  wwContext = [[N3DContextManager new] mainContext];
  fileContext = RiBegin("FileContext", "renderer", &archive, "filepath", &filename, "format", &bintype, RI_NULL);

  // process the file name for a custom display line such that
  // "prman <<filename>>.rib" will put the resulting image somewhere
  // predictably useful.
  strcpy(buf, filename);

  // remove the .rib extension from the path returned by the SavePanel
  strrchr(buf,'.')[0]='\0';

  // feed to NXPrintf to put in the custom Display command
  RiArchiveRecord("comment", "This RIB file generated by Michael B. Johnson's WW3DKit software running atop NEXTSTEP.");

  RiArchiveRecord("comment", "# Contact him at wave@media.mit.edu or (617) 666-4119 for more information.\n#\n");
  RiArchiveRecord("comment", "Display \"%s.tiff\" \"file\" \"rgba\"\n", buf);
  RiArchiveRecord("comment", "ShadingRate %f\n", sRate);
  RiArchiveRecord("comment", "Hider \"%s\"\n", hiderName);
  RiArchiveRecord("comment", "PixelSamples %f %f\n", pixelSampleX, pixelSampleY);

  [self getFrame:&myFrame];
  if ((selectionRegion.size.width > selectionWidthEpsilon)||(selectionRegion.size.height > selectionHeightEpsilon))
  {  if (selectionRegion.origin.x < 0.0)
     {  cropMinX = 0.0;
     }
     else
     {  cropMinX = selectionRegion.origin.x/myFrame.size.width;
     }
     if ((selectionRegion.size.width + selectionRegion.origin.x) > myFrame.size.width)
     {  cropMaxX = 1.0;
     }
     else
     {  cropMaxX = (selectionRegion.size.width + selectionRegion.origin.x)/myFrame.size.width;
     }
     //////// Y (note Y is flipped) //////////
     if (selectionRegion.origin.y < 0.0)
     {  cropMaxY = 1.0;
     }
     else
     {  cropMaxY = 1.0 - (selectionRegion.origin.y/myFrame.size.width);
     }
     if ((selectionRegion.size.height + selectionRegion.origin.y) > myFrame.size.height)
     {  cropMinY = 0.0;
     }
     else
     {  cropMinY = 1.0 - ((selectionRegion.size.height + selectionRegion.origin.y)/myFrame.size.height);
     }
     if (cropMinX < 0.0) { cropMinX = 0.0; } 
     if (cropMinY < 0.0) { cropMinY = 0.0; } 
     if (cropMaxX > 1.0) { cropMaxX = 1.0; } 
     if (cropMaxY > 1.0) { cropMaxY = 1.0; } 

     RiCropWindow(cropMinX, cropMaxX, cropMinY, cropMaxY); 
  }

  // need to dump all my camera state here...

  // now need to tell my worldShape to render itself...

  return self;
}


- dumpRIBToFile:(char *)filename usingShadingRate:(float)sRate hider:(char *)hiderName pixelSamples:(float)pixelSampleX :(float)pixelSampleY
{
  NXStream  *ts;
  NXRect    myFrame;
  char      buf[MAXPATHLEN+1];
  float     cropMinX, cropMinY, cropMaxX, cropMaxY;

  dumpingToRIB = YES;

  if (statusText) // potentially record activity
  {  sprintf(statusBuf, "dumping RIB to file %s...", filename);
     [statusText setStringValue:statusBuf];
  }

  // set the output format:
  if (binaryRIB)
  {  [self dumpBinaryRIBToFile:filename usingShadingRate:sRate hider:hiderName pixelSamples:pixelSampleX :pixelSampleY];
  }
  else
  {  ts = NXOpenMemory(NULL, 0, NX_WRITEONLY);

     // process the file name for a custom display line such that
     // "prman <<filename>>.rib" will put the resulting image somewhere
     // predictably useful.
     strcpy(buf, filename);

     // remove the .rib extension from the path returned by the SavePanel
     strrchr(buf,'.')[0]='\0';

     // feed to NXPrintf to put in the custom Display command
     NXPrintf(ts, "# This RIB file generated by Michael B. Johnson's WW3DKit software running atop NEXTSTEP.\n");
     NXPrintf(ts, "# Contact him at wave@media.mit.edu or (617) 666-4119 for more information.\n#\n");
     NXPrintf(ts, "Display \"%s.tiff\" \"file\" \"rgba\"\n", buf);
     NXPrintf(ts, "ShadingRate %f\n", sRate);
     NXPrintf(ts, "Hider \"%s\"\n", hiderName);
     NXPrintf(ts, "PixelSamples %f %f\n", pixelSampleX, pixelSampleY);
     [self getFrame:&myFrame];
   
     if ((selectionRegion.size.width > selectionWidthEpsilon)||(selectionRegion.size.height > selectionHeightEpsilon))
     {  //NXLogError("\n\nselectionRegion.origin == (%f, %f)\t\tselectionRegion.size == (%f, %f)\n", 
        //		selectionRegion.origin.x, selectionRegion.origin.y, 
        //		selectionRegion.size.width, selectionRegion.size.height);
        //NXLogError("myFrame.origin         == (%f, %f)\t\tmyFrame.size         == (%f, %f)\n", 
        //		myFrame.origin.x, myFrame.origin.y, 
        //		myFrame.size.width, myFrame.size.height);
        //////// X //////////
        if (selectionRegion.origin.x < 0.0)
        {  cropMinX = 0.0;
        }
        else
        {  cropMinX = selectionRegion.origin.x/myFrame.size.width;
        }
   
        if ((selectionRegion.size.width + selectionRegion.origin.x) > myFrame.size.width)
        {  cropMaxX = 1.0;
        }
        else
        {  cropMaxX = (selectionRegion.size.width + selectionRegion.origin.x)/myFrame.size.width;
        }
   
        //////// Y (note Y is flipped) //////////
        if (selectionRegion.origin.y < 0.0)
        {  cropMaxY = 1.0;
        }
        else
        {  cropMaxY = 1.0 - (selectionRegion.origin.y/myFrame.size.width);
        }
   
        if ((selectionRegion.size.height + selectionRegion.origin.y) > myFrame.size.height)
        {  cropMinY = 0.0;
        }
        else
        {  cropMinY = 1.0 - ((selectionRegion.size.height + selectionRegion.origin.y)/myFrame.size.height);
        }
   
        //NXLogError("CropWindow %f %f %f %f\n", cropMinX, cropMaxX, cropMinY, cropMaxY); 
   
        if (cropMinX < 0.0) { cropMinX = 0.0; } 
        if (cropMinY < 0.0) { cropMinY = 0.0; } 
        if (cropMaxX > 1.0) { cropMaxX = 1.0; } 
        if (cropMaxY > 1.0) { cropMaxY = 1.0; } 
   
        NXPrintf(ts, "CropWindow %f %f %f %f\n", cropMinX, cropMaxX, cropMinY, cropMaxY); 
     }
     // then feed the rib code to the stream and
     [self copyRIBCode:ts];
     // save the stream to the file selected in the savepanel
     NXSaveToFile(ts, filename);
   
     // and close the stream (which also flushes it), also making sure
     // that the allocated memory is freed.
     NXCloseMemory(ts,NX_FREEBUFFER);
   
     if (statusText) // potentially record activity
     {  sprintf(statusBuf, "done!");
        [statusText setStringValue:statusBuf];
     }
  }

  dumpingToRIB = NO;
  return self;
}


- dumpRIBToFile:(char *)filename { return [self dumpRIBToFile:filename usingShadingRate:shadingRate hider:"hidden" pixelSamples:2 :2]; }


// when the well gets resized, I have to catch this and tell
// theRotator about it, cause the default doesn't seem to do it (cause it
// ain't a subview, natch)...
- sizeTo:(NXCoord)deltaWidth :(NXCoord)deltaHeight
{
  NXPoint  aPoint;
  float    radius;


  [super sizeTo:deltaWidth :deltaHeight];

  if (deltaWidth > deltaHeight)
  {  radius = deltaHeight / 2.0;
  }
  else
  {  radius = deltaWidth / 2.0;
  }
  aPoint.x = deltaWidth/2.0;
  aPoint.y = deltaHeight/2.0;

  [theRotator setCenter:&aPoint andRadius:radius];

  return self;
}

- setImageFile:(const char *)filename 
{
    // It's actually a bad idea to free this image, because it might be shared...
    // in this case, screw it, cause this has a very specific use...
   if (filename && *filename)
   {  image = [NXImage findImageNamed:filename];
      if (!image) 
      {  image = [[NXImage alloc] init];
         [image setDataRetained:YES];
         if (![image loadFromFile:filename])
         {  [self setImage:nil];
            NXLogError("unable to load image from file <%s>\n", filename);
            return nil; 
         }
      }
   }
   else
   {  [self setImage:nil];
      return nil;
   }

   return self;
}

- setImage:i  
{  //if (image) 
   //{ [image free]; 
   //} 
   image = i; 
   return self; 
}

#define SIZE	1
static float pattern[SIZE] = {2.};
static float offset = 0;

- drawPS:(NXRect *)rects :(int)nRects
{
  NXPoint p = {0.0, 0.0};
  NXSize  s;

   if ((selectionRegion.size.width > selectionWidthEpsilon)||(selectionRegion.size.height > selectionHeightEpsilon))
   {  PSsetgray(NX_WHITE);
      PSsetlinewidth(0.0);
      PSsetdash(pattern,SIZE, offset);
      PSrectstroke(selectionRegion.origin.x, selectionRegion.origin.y,  
		   selectionRegion.size.width, selectionRegion.size.height);

      p.x = ((selectionRegion.origin.x) > 0) ? (selectionRegion.origin.x):0;
      p.y = ((selectionRegion.origin.y) > 0) ? (selectionRegion.origin.y):0;
  }

  // there is a bug in 3.2 HP that gets tickled when you have a View
  // that is 32 bits deep and has qrman drawing into it.  Since I don't
  // *really* use the fact that there is an alpha component to this image
  // when I composite it in here, I'm going to punt on doing a "source over"
  // operation, and just copy it into the View.

  if (image)
  {  //NXSetColor(NX_COLORBLACK);
     //PSsetalpha(1.0);
     [image getSize:&s];
     PScompositerect(p.x, p.y, s.width, s.height, NX_COPY); // used to be PScompositerect(p.x, p.y, s.width, s.height, NX_SOVER);
     [image composite:NX_COPY toPoint:&p]; //used to be [image composite:NX_SOVER toPoint:&p];
  }
  return self;
}


// need to add timed entries if I'm still holding the modifier down
// when I exit the inner modal loop.  Use the velocity at exit to set the
// frequency of the timed entry...
// When I reenter the mouseDown loop, I should remove any outstanding timed entries.
// when doing a timed entry, I should record frame rate and display it on the status bar

- removeAnimateTE
{
  if (animateTE)
  {  DPSRemoveTimedEntry(animateTE);
     animateTE = 0;
     animateRotate = NO;
     // switch back to the N3D_SmoothSolids surface type
     [self setSurfaceTypeForAll:renderStyle chooseHider:YES];	
     RiGeometricApproximation(RI_TESSELATION, RI_PARAMETRIC, tesselationVector, RI_NULL);
  }
  return self;
}


- animateRotate
{
  [theRotator trackMouseFrom:&oldMouse to:&newMouse rotationMatrix:rmat andInverse:irmat];
  [worldShape concatTransformMatrix:rmat premultiply:YES];
  oldMouse.x =  newMouse.x;
  oldMouse.y = newMouse.y;
  newMouse.x += dMouse.x;
  newMouse.y += dMouse.y;
  // NXLogError("old == (%f, %f) new == (%f, %f)\n", oldMouse.x, oldMouse.y, newMouse.x, newMouse.y);
  return self;
}


- animateScale
{
  return self;
}


- animateTranslate
{
  return self;
}


- animateClick
{
  if (animateRotate) { [self animateRotate]; }
  if (animateScale) { [self animateScale]; }
  if (animateTranslate) { [self animateTranslate]; }
  [self display];
  return self;
}


void animateHandler(DPSTimedEntry teNumber, double now, void *userData)
{
  id  myObj = (id)userData;

  [myObj animateClick];
  return ;
}


- removeRenderTE
{
  if (renderTE)
  {  DPSRemoveTimedEntry(renderTE);
     renderTE = 0;
     renderCount = 0;
     backgroundRendering = NO;
  }
  return self;
}


- setBackgroundRenderingOff  { [self removeRenderTE]; return self; }

- (BOOL)backgroundRendering { return backgroundRendering; }

- backgroundRender
{
  if (!backgroundRendering)
  {  renderCount++;
     if (renderCount == renderAtMediumRate)
     {  NXLogError("starting medium render...\n");
        backgroundRendering = YES;
        [delegate setRenderStartTime];
        [self renderAsTIFF];
     }
     if (renderCount == renderAtHighRate)
     {  NXLogError("starting high render...\n");
        backgroundRendering = YES;
        [delegate setRenderStartTime];
        [self renderAsTIFF];
     }
  }
  return self;
}


void renderHandler(DPSTimedEntry teNumber, double now, void *userData)
{
  id  myObj = (id)userData;

  [myObj backgroundRender];
  return ;
}


////////////////////////////////////////////////////////////////////
//			So user can get some feedback
///////////////////////////////////////////////////////////////////

static void getRegion(NXRect *region, const NXPoint *p1, const NXPoint  *p2)
/*
 * Returns the rectangle which has p1 and p2 as its corners.
 */
{
    region->size.width = p1->x - p2->x;
    region->size.height = p1->y - p2->y;
    if (region->size.width < 0.0) {
	region->origin.x = p2->x + region->size.width;
	region->size.width = ABS(region->size.width);
    } else {
	region->origin.x = p2->x;
    }
    if (region->size.height < 0.0) {
	region->origin.y = p2->y + region->size.height;
	region->size.height = ABS(region->size.height);
    } else {
	region->origin.y = p2->y;
    }
}

- updateShapeForInteraction:(BOOL)provideUpdates whenTracking:(BOOL)trackShape
{
    updateShape = provideUpdates;
    updateWhenTracking = trackShape;
    return self;
}

- shouldChangeSelected:(BOOL)changeSelected
{
    changeSelectedShape = changeSelected;
    return self;
}

// setCurrentShape:theShape

#define ACTIVEBUTTONMASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_ALTERNATEMASK|NX_CONTROLMASK|NX_COMMANDMASK)
- mouseDown:(NXEvent *)theEvent
{
  int        oldMask;
  //NXPoint    p, last, start;
  //NXRect     visibleRect, oldRegion;
  NXPoint    start;
  RtPoint    myEyePoint, 
             previousEyePoint, previousViewPoint;
  float      previousRoll;
  //NXRect selectionRect;
  long         tmpUsec;
  struct
    timeval  renderStartTime, renderFinishTime;
  char       timeBuf[256];
  struct
      timeval  elapsedTime;
  int          frameCount = 0;
  float        currentFPS;


  [self removeAnimateTE];
  [self removeRenderTE];
  if (image)
  {  [image free];
     image = nil;
  }

  dMouse.x = 0.0;
  dMouse.y = 0.0;

  // track the mouse until a mouseUp event occurs, updating the display
  // as tracking happens.
  [self lockFocus];
  oldMask = [window addToEventMask:ACTIVEBUTTONMASK];
  
  // switch to the N3D_WireFrame surface type
  [self setSurfaceTypeForAll:movingRenderStyle chooseHider:YES];	
  RiGeometricApproximation(RI_TESSELATION, RI_PARAMETRIC, lowRezTesselationVector, RI_NULL);
  
  oldMouse = theEvent->location;
  [self convertPoint:&oldMouse fromView:nil];
  start = oldMouse;
    
    /*
     * Update the current shape should changeSelectedShape be enabled.
     * setCurrentShape to be complete, or setNoCurrentShape should
     * the mouseDown occur outside of any bounding box (for those
     * shapes that are selectable).
     */
    if (changeSelectedShape) {
	id theShape = worldShape;
	NXRect boundingRect;
	
	while (theShape) {
	    if ([theShape conformsTo:@protocol(WWRenderable)]) {
		(void)[theShape boundingBox];
	    }
	    [theShape getBounds:&boundingRect inCamera:self];
	    if (NXPointInRect(&start, &boundingRect)) {
		[self setCurrentShape:theShape];
		break;
	    }
	    theShape = [theShape nextPeer];
	}
	if (!theShape) {
	    [self setNoCurrentShape:self];
	}
#if 0
	List *selected;
	
	NXSetRect(&selectionRect, start.x, start.y, 10.0, 10.0);
	selected = [self selectShapesIn:&selectionRect];
	if ([selected count] == 0) {
	    [self setNoCurrentShape:self];
	} else {
	    [self setCurrentShape:[selected objectAt:0]];
	}
#endif
    }
    
  // when the alt key is depressed, we scale the worldShape up or down, depending on mouse movement.
  // otherwise, we rotate the worldShape using the virtual trackball

  gettimeofday(&renderStartTime, 0);
  while (1)
  { newMouse = theEvent->location;
    [self convertPoint:&newMouse fromView:nil];
    dMouse.x = newMouse.x - oldMouse.x;
    dMouse.y = newMouse.y - oldMouse.y;
    if (dMouse.x != 0.0 || dMouse.y != 0.0) 
    {  if (theEvent->flags & NX_ALTERNATEMASK)
       {  switch (trackballAffects)
	  {  case WW_TRACKBALL_WORLD_SHAPE:
                 sprintf(statusBuf, "translating world in Z by %f", (translateZFactor * dMouse.y));
                 [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
		 [worldShape translate:0.0 :0.0 :(translateZFactor * dMouse.y)];
		 break;
	     case WW_TRACKBALL_CURRENT_SHAPE:
                 sprintf(statusBuf, "translating %s in Z by %f", [currentShape shapeName], (translateZFactor * dMouse.y));
                 [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
		 [currentShape translate:0.0 :0.0 :(translateZFactor * dMouse.y)];
		 break;
	     case WW_TRACKBALL_CAMERA:
		 // need to change Eye point
                 sprintf(statusBuf, "moving eye point in Z by %f", (translateZFactor * dMouse.y));
                 [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
		 [self moveEyeBy:0.0 :0.0 :(-1. * (translateZFactor * dMouse.y))];
		 break;
	  }
       }
       else
       {  if (theEvent->flags & NX_CONTROLMASK)
	  {  // NOTE draw rubber band selection here
	     getRegion(&selectionRegion, &newMouse, &start);
             [statusText setStringValue:"selecting crop region for rendering..."]; [statusText display]; NXPing();
	     //NXInsetRect(&oldRegion, -1.0, -1.0);
	     //oldRegion = selectionRegion;
	     // last = p;
          }
          else
	  {  if (theEvent->flags & NX_COMMANDMASK)
             {  switch (trackballAffects)
	  	{  case WW_TRACKBALL_WORLD_SHAPE:
                    sprintf(statusBuf, "translating world in X & Y by (%f, %f)", (translateXFactor * dMouse.x), (translateYFactor * dMouse.y));
                    [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
		    [worldShape translate:(translateXFactor * dMouse.x) 
                                         :(translateYFactor * dMouse.y) :0.0];
		    break;
 		  case WW_TRACKBALL_CURRENT_SHAPE:
                    sprintf(statusBuf, "translating %s in X & Y by (%f, %f)", 
                            [currentShape shapeName], (translateXFactor * dMouse.x), (translateYFactor * dMouse.y));
                    [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
		    [currentShape translate:(translateXFactor * dMouse.x) 
                                           :(translateYFactor * dMouse.y) :0.0];
                    break;
	          case WW_TRACKBALL_CAMERA:
                    sprintf(statusBuf, "moving eye in X & Y by (%f, %f)", (translateXFactor * dMouse.x), (translateYFactor * dMouse.y));
                    [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
                    [self moveEyeBy:(-1. * (translateXFactor * dMouse.x)) 
 	 	                   :(-1. * (translateYFactor * dMouse.y)) :0.0];
		    break;
	        }
	     }
             else
             {  [theRotator trackMouseFrom:&oldMouse to:&newMouse rotationMatrix:rmat andInverse:irmat];
                switch (trackballAffects)
		{  case WW_TRACKBALL_WORLD_SHAPE:
                       sprintf(statusBuf, "rotating world...");
                       [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
		       [worldShape concatTransformMatrix:rmat premultiply:YES];
                       break;
		   case WW_TRACKBALL_CAMERA:
                       sprintf(statusBuf, "rotating camera...");
                       [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
		       [self getEyeAt:&previousEyePoint toward:&previousViewPoint roll:&previousRoll];   
                       myEyePoint[0] = previousEyePoint[0] + (-1. * (translateXFactor * dMouse.x)); 
                       myEyePoint[1] = previousEyePoint[1] + (-1. * (translateYFactor * dMouse.y)); 
                       myEyePoint[2] = previousEyePoint[2];
		       [self setEyeAt:myEyePoint toward:previousViewPoint roll:previousRoll];   
                       break;
		   case WW_TRACKBALL_CURRENT_SHAPE:
                       sprintf(statusBuf, "rotating %s...", [currentShape shapeName]);
                       [statusText setStringValue:statusBuf]; [statusText display]; NXPing();
		       [currentShape concatTransformMatrix:rmat premultiply:YES];
                       break;
		}
	     } 
	  }
       }
	if (updateShape && updateWhenTracking) {
	    if (trackballAffects == WW_TRACKBALL_WORLD_SHAPE) {
		[worldShape performUpdateForInteraction:self];
	    } else if (trackballAffects == WW_TRACKBALL_CURRENT_SHAPE) {
		[currentShape performUpdateForInteraction:self];
	    }
	}
       [self display];
       NXPing();
    }
    theEvent = [NXApp getNextEvent:ACTIVEBUTTONMASK];
    if (theEvent->type == NX_MOUSEUP)
    {  break;
    }
    else
    {  oldMouse = newMouse;
    }
    frameCount++;
  }
  gettimeofday(&renderFinishTime, 0);
  [statusText setStringValue:"done"]; [statusText display]; NXPing();

  elapsedTime.tv_sec = renderFinishTime.tv_sec  - renderStartTime.tv_sec;
  tmpUsec = renderFinishTime.tv_usec - renderStartTime.tv_usec;
  if (tmpUsec < 0)
  {  elapsedTime.tv_sec--;
     elapsedTime.tv_usec = (long)1000000 + tmpUsec;
  }
  else
  {  elapsedTime.tv_usec = tmpUsec;
  }
  if (elapsedTime.tv_usec > (long)1000000)
  {  elapsedTime.tv_sec++;
     elapsedTime.tv_usec -= (long)1000000;
  }

  if (frameCount)
  {  currentFPS = (float)frameCount/((float)elapsedTime.tv_sec + ((float)(elapsedTime.tv_usec)/1000000.));
     sprintf(timeBuf, 
	     "%f fps: %d frames in %d.%d seconds", 
	     currentFPS, frameCount, (int)(elapsedTime.tv_sec), (int)((1000000 - elapsedTime.tv_usec)/100000));
     [statusText setStringValue:timeBuf]; [statusText display]; NXPing();
  }

  // check to see if we should go off and render (need a minimum image size, like 16x16)...

  // check to see if we should keep rotating or scaling...
  // if mouse was moving significantly, keep going...
  if (((float)fabs((double)(dMouse.x)) > epsilon) || ((float)fabs((double)dMouse.y) > epsilon))
  {  // comment out for now...
     //animateTE = DPSAddTimedEntry(.002, (DPSTimedEntryProc)animateHandler, self, NX_BASETHRESHOLD);
     //animateRotate = YES;
     [self setSurfaceTypeForAll:renderStyle chooseHider:YES];	
     RiGeometricApproximation(RI_TESSELATION, RI_PARAMETRIC, tesselationVector, RI_NULL);
  }
  else
  {  animateTE = 0;
     animateRotate = NO;
     // switch back to the N3D_SmoothSolids surface type
     [self setSurfaceTypeForAll:renderStyle chooseHider:YES];	
     RiGeometricApproximation(RI_TESSELATION, RI_PARAMETRIC, tesselationVector, RI_NULL);

     //renderTE = DPSAddTimedEntry(renderCheckTimeSlice, (DPSTimedEntryProc)renderHandler, self, NX_BASETHRESHOLD);
  }
    if (updateShape) {
	if (trackballAffects == WW_TRACKBALL_WORLD_SHAPE) {
	    [worldShape performUpdateForInteraction:self];
	} else if (trackballAffects == WW_TRACKBALL_CURRENT_SHAPE) {
	    [currentShape performUpdateForInteraction:self];
	}
    }
  [self display];
  [self unlockFocus];
  [window setEventMask:oldMask];
  [window flushWindow];

  return self;
}

- (BOOL)acceptsFirstResponder { return YES; }

- (BOOL)acceptsFirstMouse { return YES; }

- keyDown:(NXEvent *)theEvent
{
//  id  theCmd = nil;


  // what we want to be able to do is have a set of mappings from the
  // charCode to some piece of tcl code.
  // We also want to be able to have the tcl code be executed in either
  // the camera's interp or the shape interp...
  // we'll say that if the cmd key is held down, it goes to the camera,
  // otherwise we send it to the root shape's interp.

  // we should probably have a hash table which has two things:
  // the charCode and the tcl code that corresponds to it
  // if there isn't an entry, drop through...

//  if (theEvent->flags & NX_COMMANDMASK)  // the command key is being held down : camera!
//  {  theCmd = [self cameraCmdFromCharCode:theEvent->data.key.charCode];
//     if (theCmd)
//     {  [tclInterp globalEval:[theCmd str]];
//     }
//     [delegate controlsWereUpdated:self];
//     return self;
//  }
//  else // must want to talk to the model, I guess...
//  {  theCmd = [self modelCmdFromCharCode:theEvent->data.key.charCode];
//     if (theCmd)
//     {  [[rootShape tclInterp] globalEval:[theCmd str]];
//     }
//     [delegate controlsWereUpdated:self];
//     return self;
//  }

  return [super keyDown:theEvent];
}

- takeRenderWorldAsBox:sender 
{ 
   drawWorldAsBox = [sender intValue]; 
   [worldShape setDrawAsBox:drawWorldAsBox];
   return [self display]; 
}
- takeRenderCurrentAsBox:sender 
{
   drawCurrentAsBox = [sender intValue]; 
   [currentShape setDrawAsBox:drawCurrentAsBox];
   return [self display]; 
}

- takeWorldIsVisible:sender { [worldShape setVisible:[sender intValue]]; return [self display]; }
- takeCurrentIsVisible:sender { [currentShape setVisible:[sender intValue]]; return [self display]; }

- (BOOL)renderWorldAsBox { return [worldShape doesDrawAsBox]; }
- (BOOL)renderCurrentAsBox { return [currentShape doesDrawAsBox]; }
- (BOOL)worldIsVisible { return [worldShape isVisible]; }
- (BOOL)currentIsVisible { return [currentShape isVisible]; }

- (int)trackballAffects { return trackballAffects; }
- takeTrackballAffectsFromMatrix:sender
{
  int  newVal = [[sender selectedCell] tag];


  switch (newVal)
  {  case WW_TRACKBALL_WORLD_SHAPE:
	 trackballAffects = newVal;
         [self setUsePreTransformMatrix:NO];
	 break;
     case WW_TRACKBALL_CAMERA:
	 // we assume that [self usesPreTransformMatrix] returns YES...
	 trackballAffects = newVal;
         [self setUsePreTransformMatrix:YES];
 	 break;
     case WW_TRACKBALL_CURRENT_SHAPE:
	 trackballAffects = newVal;
         [self setUsePreTransformMatrix:NO];
	 break;
     default:
	 NXLogError("%d is an invalid value for trackballAffects.\n", newVal);
     } 


  return self;
}

- (int)trackballXYZ 
{ 
  N3DAxis  theAxis  = [theRotator rotationAxis];
  int      theAxisInt = -1;


  if (theAxis == N3D_AllAxes) { theAxisInt = 0; }

  if (theAxis == N3D_XAxis) { theAxisInt = 1; }
  if (theAxis == N3D_YAxis) { theAxisInt = 2; }
  if (theAxis == N3D_ZAxis) { theAxisInt = 3; }

  if (theAxis == N3D_XYAxes) { theAxisInt = 4; }
  if (theAxis == N3D_XZAxes) { theAxisInt = 5; }
  if (theAxis == N3D_YZAxes) { theAxisInt = 6; }
 
  return theAxisInt;
}

- takeTrackballXYZFromMatrix:sender
{
  int  newVal = [[sender selectedCell] tag];

  switch (newVal)
  {  case 0:
	 [theRotator setRotationAxis:N3D_AllAxes];
	 break;
     case 1:
	 [theRotator setRotationAxis:N3D_XAxis];
	 break;
     case 2:
	 [theRotator setRotationAxis:N3D_YAxis];
	 break;
     case 3:
	 [theRotator setRotationAxis:N3D_ZAxis];
	 break;
     case 4:
	 [theRotator setRotationAxis:N3D_XYAxes];
	 break;
     case 5:
	 [theRotator setRotationAxis:N3D_XZAxes];
	 break;
     case 6:
	 [theRotator setRotationAxis:N3D_YZAxes];
	 break;
     default:
	 NXLogError("%d is an invalid value for trackballXYZ.\n", newVal);
     } 
  return self;
}


- tclInterp { return tclInterp; }


/// new camera stuff
- takeFocalLength:sender  {  return [self setFocalLength:[sender floatValue]];  }
- takeFocalDistance:sender {  return [self setFocalDistance:[sender floatValue]]; }
- takeFStop:sender {  return [self setFStop:[sender floatValue]]; }
- takeExposureLength:sender {  return [self setExposureLength:[sender floatValue]]; }

- takeShotOutputTypeFromMatrix:sender 
{  NXLogError("[WW3DCamera takeShotOutputTypeFromMatrix:sender] is not really implemented yet.\n");
   shotOutputType = [[sender selectedCell] tag]; 
   return self;
}

- takeShotLength:sender  {  return [self setShotLength:[sender floatValue]];  }
- takeFramesPerSecond:sender {  return [self setFramesPerSecond:[sender floatValue]]; }
- takeFrameNumber:sender {  return [self setFrameNumber:[sender intValue]]; }

- takeExposureLengthFactor:sender { return [self setExposureLengthFactor:[sender floatValue]]; }
- takeShotStartTime:sender  {  return [self setShotStartTime:[sender floatValue]];  }


- setRenderStyle:(int)s { renderStyle = s; [self setSurfaceTypeForAll:renderStyle chooseHider:YES]; [self display]; return self; }
- (int)renderStyle { return renderStyle; }

- setMovingRenderStyle:(int)s { movingRenderStyle = s; return self; }
- (int)movingRenderStyle { return movingRenderStyle; }

- (NXColor) backgroundColor 	{ return backgroundColor; }
- setBackgroundColor:(NXColor)c { backgroundColor = c; [self display]; return self; }

- (BOOL)showSelectedShape { return showSelectedShape; }
- setShowSelectedShape:(BOOL)flag 
{ 
   showSelectedShape = flag; 
   [currentShape setSelected:flag andDrawOrigin:drawOriginForSelectedShape];
   [self display];
   return self; 
}

- setSceneClock:newSceneClock { sceneClock = newSceneClock; return self; }
- sceneClock { return sceneClock; }

- (BOOL)drawOriginForSelectedShape { return drawOriginForSelectedShape; }
- setDrawOriginForSelectedShape:(BOOL)flag 
{ 
   drawOriginForSelectedShape = flag; 
   [currentShape setDrawOrigin:flag];
   [self display];
   return self; 
}

- otherLightList { return otherLightList; }

- addLocalLight:light usingPath:(const char *)aParentPath 
{ 
  id theLightParent = [[self worldShape] getChildGivenPath:aParentPath];


  if (theLightParent)
  {  [theLightParent addChild:light];
     [otherLightList addObject:light];
     return self;
  }
  return nil;
}

- setStatusText:newStatusText { statusText = newStatusText; return self; }
- statusText { return statusText; }

- (BOOL)useRendribInstead { return useRendribInstead; }
- setUseRendribInstead:(BOOL)n { useRendribInstead = n; return self; }
- takeUseRendribInstead:sender { useRendribInstead = [sender intValue]; return self; }

- (BOOL)binaryRIB { return binaryRIB; }
- setBinaryRIB:(BOOL)n { binaryRIB = n; return self; }
- takeBinaryRIB:sender { binaryRIB = [sender intValue]; return self; }


#define typeVectorVersion1 "i*ffffffff@[2f][2f]fiifcci@cffffffff@@@"
#define typeValuesVersion1 &renderStyle, &ribName, \
        &scaleUpFactor, &scaleDownFactor, &translateXFactor, &translateYFactor, &translateZFactor, \
        &epsilon, &selectionWidthEpsilon, &selectionHeightEpsilon, \
        &image, &lowRezTesselationVector, &tesselationVector, &shadingRate, \
        &renderAtMediumRate, &renderAtHighRate, &renderCheckTimeSlice, &showSelectedShape, &drawOriginForSelectedShape, \
        &movingRenderStyle, &tclInterp, &defaultLightsInUse, \
        &fStop, &focalLength, &focalDistance, &shutterOpenTime, &exposureLength, &frameTimeIncrement, &shotLength, &framesPerSecond, \
        &ambientLight, &leftLight, &rightLight


#define typeVectorVersion2 "i*ffffffff@[2f][2f]fiifcci@cffffffff@@@"
#define typeValuesVersion2 &renderStyle, &ribName, \
        &scaleUpFactor, &scaleDownFactor, &translateXFactor, &translateYFactor, &translateZFactor, \
        &epsilon, &selectionWidthEpsilon, &selectionHeightEpsilon, \
        &image, &lowRezTesselationVector, &tesselationVector, &shadingRate, \
        &renderAtMediumRate, &renderAtHighRate, &renderCheckTimeSlice, &showSelectedShape, &drawOriginForSelectedShape, \
        &movingRenderStyle, &tclInterp, &defaultLightsInUse, \
        &fStop, &focalLength, &focalDistance, &shutterOpenTime, &exposureLength, &frameTimeIncrement, &shotLength, &framesPerSecond, \
        &ambientLight, &leftLight, &rightLight


#define typeVectorVersion3 "i*ffffffff@fiifcci@cffffffff@@@"
#define typeValuesVersion3 &renderStyle, &ribName, \
        &scaleUpFactor, &scaleDownFactor, &translateXFactor, &translateYFactor, &translateZFactor, \
        &epsilon, &selectionWidthEpsilon, &selectionHeightEpsilon, \
        &image, &shadingRate, \
        &renderAtMediumRate, &renderAtHighRate, &renderCheckTimeSlice, &showSelectedShape, &drawOriginForSelectedShape, \
        &movingRenderStyle, &tclInterp, &defaultLightsInUse, \
        &fStop, &focalLength, &focalDistance, &shutterOpenTime, &exposureLength, &frameTimeIncrement, &shotLength, &framesPerSecond, \
        &ambientLight, &leftLight, &rightLight

#define typeVector "i*ffffffff@fiifcci@cffffffff@@@ff"
#define typeValues &renderStyle, &ribName, \
        &scaleUpFactor, &scaleDownFactor, &translateXFactor, &translateYFactor, &translateZFactor, \
        &epsilon, &selectionWidthEpsilon, &selectionHeightEpsilon, \
        &image, &shadingRate, \
        &renderAtMediumRate, &renderAtHighRate, &renderCheckTimeSlice, &showSelectedShape, &drawOriginForSelectedShape, \
        &movingRenderStyle, &tclInterp, &defaultLightsInUse, \
        &fStop, &focalLength, &focalDistance, &shutterOpenTime, &exposureLength, &frameTimeIncrement, &shotLength, &framesPerSecond, \
        &ambientLight, &leftLight, &rightLight, &shotStartTime, &exposureLengthFactor


- read:(NXTypedStream *)stream
{
    int version;
    
    [super read:stream];
    
    NX_DURING
	version = NXTypedStreamClassVersion(stream, "WW3DCamera");
	if (version == 0) NXReadTypes(stream,"i",&version), version=1;
	if (version == 1) {
	    NXReadTypes(stream, typeVectorVersion1, typeValuesVersion1);
	    backgroundColor = NXReadColor(stream);
	    ribColor = NXReadColor(stream);
	    shotStartTime = 0.0;
	    exposureLengthFactor = 1.0;
            binaryRIB = NO;
	}
	if (version == 2) {
	    NXReadTypes(stream, typeVectorVersion2, typeValuesVersion2);
	    backgroundColor = NXReadColor(stream);
	    ribColor = NXReadColor(stream);
	    shotStartTime = 0.0;
	    exposureLengthFactor = 1.0;
            binaryRIB = YES;
	}
	if (version == 3) {
	    NXReadTypes(stream, typeVectorVersion3, typeValuesVersion3);
	    NXReadArray(stream, "f", 2, lowRezTesselationVector);
	    NXReadArray(stream, "f", 2, tesselationVector);
	    backgroundColor = NXReadColor(stream);
	    ribColor = NXReadColor(stream);
	    sceneClock = NXReadObject(stream);
	    shotStartTime = 0.0;
	    exposureLengthFactor = 1.0;
            binaryRIB = NO;
	}
	if (version == 4) {
	    NXReadTypes(stream, typeVector, typeValues);
	    NXReadArray(stream, "f", 2, lowRezTesselationVector);
	    NXReadArray(stream, "f", 2, tesselationVector);
	    backgroundColor = NXReadColor(stream);
	    ribColor = NXReadColor(stream);
	    sceneClock = NXReadObject(stream);
            binaryRIB = NO;
	}
	if (version == 5) {
	    NXReadTypes(stream, typeVector, typeValues);
	    NXReadArray(stream, "f", 2, lowRezTesselationVector);
	    NXReadArray(stream, "f", 2, tesselationVector);
	    backgroundColor = NXReadColor(stream);
	    ribColor = NXReadColor(stream);
	    sceneClock = NXReadObject(stream);
	    NXReadType(stream, "c", &binaryRIB);
	}
    NX_HANDLER
	NXLogError("in read: %s, exception [%d] raised.\n", [[self class] name], NXLocalHandler.code);
	return nil;
    NX_ENDHANDLER
    return self;
}

- write:(NXTypedStream *)stream 
{
    [super write:stream];
    exposureLength = savedExposureLength;  // just to make sure we archive it...
    NXWriteTypes(stream, typeVector, typeValues);
    NXWriteArray(stream, "f", 2, lowRezTesselationVector);
    NXWriteArray(stream, "f", 2, tesselationVector);
    NXWriteColor(stream, backgroundColor);
    NXWriteColor(stream, ribColor);
    NXWriteObjectReference(stream, sceneClock);
    NXWriteType(stream, "c", &binaryRIB);
    return self;
}

@end
